summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:59:48 +0000
commit3b9b6d0b8e7f798023c9d109c490449d528fde80 (patch)
tree2e1c188dd7b8d7475cd163de9ae02c428343669b /lib
parentInitial commit. (diff)
downloadbind9-3b9b6d0b8e7f798023c9d109c490449d528fde80.tar.xz
bind9-3b9b6d0b8e7f798023c9d109c490449d528fde80.zip
Adding upstream version 1:9.18.19.upstream/1%9.18.19upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am3
-rw-r--r--lib/Makefile.in795
-rw-r--r--lib/bind9/Makefile.am36
-rw-r--r--lib/bind9/Makefile.in901
-rw-r--r--lib/bind9/check.c6072
-rw-r--r--lib/bind9/getaddresses.c164
-rw-r--r--lib/bind9/include/bind9/check.h65
-rw-r--r--lib/bind9/include/bind9/getaddresses.h51
-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
-rw-r--r--lib/irs/Makefile.am27
-rw-r--r--lib/irs/Makefile.in889
-rw-r--r--lib/irs/include/irs/resconf.h140
-rw-r--r--lib/irs/resconf.c713
-rw-r--r--lib/isc/Makefile.am253
-rw-r--r--lib/isc/Makefile.in2073
-rw-r--r--lib/isc/aes.c71
-rw-r--r--lib/isc/app.c421
-rw-r--r--lib/isc/assertions.c110
-rw-r--r--lib/isc/astack.c85
-rw-r--r--lib/isc/backtrace.c89
-rw-r--r--lib/isc/base32.c443
-rw-r--r--lib/isc/base64.c270
-rw-r--r--lib/isc/buffer.c352
-rw-r--r--lib/isc/commandline.c265
-rw-r--r--lib/isc/condition.c64
-rw-r--r--lib/isc/counter.c112
-rw-r--r--lib/isc/crc64.c140
-rw-r--r--lib/isc/dir.c268
-rw-r--r--lib/isc/entropy.c28
-rw-r--r--lib/isc/entropy_private.h37
-rw-r--r--lib/isc/errno.c24
-rw-r--r--lib/isc/errno2result.c133
-rw-r--r--lib/isc/errno2result.h34
-rw-r--r--lib/isc/error.c91
-rw-r--r--lib/isc/event.c95
-rw-r--r--lib/isc/file.c792
-rw-r--r--lib/isc/glob.c53
-rw-r--r--lib/isc/hash.c149
-rw-r--r--lib/isc/heap.c280
-rw-r--r--lib/isc/hex.c212
-rw-r--r--lib/isc/hmac.c167
-rw-r--r--lib/isc/ht.c573
-rw-r--r--lib/isc/httpd.c1147
-rw-r--r--lib/isc/include/isc/aes.h40
-rw-r--r--lib/isc/include/isc/align.h20
-rw-r--r--lib/isc/include/isc/app.h281
-rw-r--r--lib/isc/include/isc/assertions.h73
-rw-r--r--lib/isc/include/isc/astack.h49
-rw-r--r--lib/isc/include/isc/atomic.h79
-rw-r--r--lib/isc/include/isc/attributes.h82
-rw-r--r--lib/isc/include/isc/backtrace.h101
-rw-r--r--lib/isc/include/isc/barrier.h39
-rw-r--r--lib/isc/include/isc/base32.h143
-rw-r--r--lib/isc/include/isc/base64.h98
-rw-r--r--lib/isc/include/isc/buffer.h1023
-rw-r--r--lib/isc/include/isc/cmocka.h55
-rw-r--r--lib/isc/include/isc/commandline.h56
-rw-r--r--lib/isc/include/isc/condition.h53
-rw-r--r--lib/isc/include/isc/counter.h84
-rw-r--r--lib/isc/include/isc/crc64.h55
-rw-r--r--lib/isc/include/isc/deprecated.h20
-rw-r--r--lib/isc/include/isc/dir.h80
-rw-r--r--lib/isc/include/isc/endian.h166
-rw-r--r--lib/isc/include/isc/errno.h27
-rw-r--r--lib/isc/include/isc/error.h45
-rw-r--r--lib/isc/include/isc/event.h117
-rw-r--r--lib/isc/include/isc/eventclass.h45
-rw-r--r--lib/isc/include/isc/file.h381
-rw-r--r--lib/isc/include/isc/formatcheck.h33
-rw-r--r--lib/isc/include/isc/fuzz.h23
-rw-r--r--lib/isc/include/isc/glob.h43
-rw-r--r--lib/isc/include/isc/hash.h63
-rw-r--r--lib/isc/include/isc/heap.h161
-rw-r--r--lib/isc/include/isc/hex.h98
-rw-r--r--lib/isc/include/isc/hmac.h148
-rw-r--r--lib/isc/include/isc/ht.h191
-rw-r--r--lib/isc/include/isc/httpd.h63
-rw-r--r--lib/isc/include/isc/interfaceiter.h127
-rw-r--r--lib/isc/include/isc/iterated_hash.h38
-rw-r--r--lib/isc/include/isc/lang.h24
-rw-r--r--lib/isc/include/isc/lex.h444
-rw-r--r--lib/isc/include/isc/list.h229
-rw-r--r--lib/isc/include/isc/log.h853
-rw-r--r--lib/isc/include/isc/magic.h31
-rw-r--r--lib/isc/include/isc/managers.h30
-rw-r--r--lib/isc/include/isc/md.h204
-rw-r--r--lib/isc/include/isc/mem.h572
-rw-r--r--lib/isc/include/isc/meminfo.h30
-rw-r--r--lib/isc/include/isc/mutex.h50
-rw-r--r--lib/isc/include/isc/mutexblock.h54
-rw-r--r--lib/isc/include/isc/net.h292
-rw-r--r--lib/isc/include/isc/netaddr.h197
-rw-r--r--lib/isc/include/isc/netdb.h48
-rw-r--r--lib/isc/include/isc/netmgr.h811
-rw-r--r--lib/isc/include/isc/netscope.h36
-rw-r--r--lib/isc/include/isc/nonce.h33
-rw-r--r--lib/isc/include/isc/offset.h25
-rw-r--r--lib/isc/include/isc/once.h29
-rw-r--r--lib/isc/include/isc/os.h52
-rw-r--r--lib/isc/include/isc/parseint.h57
-rw-r--r--lib/isc/include/isc/pool.h138
-rw-r--r--lib/isc/include/isc/portset.h135
-rw-r--r--lib/isc/include/isc/print.h29
-rw-r--r--lib/isc/include/isc/quota.h154
-rw-r--r--lib/isc/include/isc/radix.h221
-rw-r--r--lib/isc/include/isc/random.h65
-rw-r--r--lib/isc/include/isc/ratelimiter.h145
-rw-r--r--lib/isc/include/isc/refcount.h244
-rw-r--r--lib/isc/include/isc/regex.h33
-rw-r--r--lib/isc/include/isc/region.h95
-rw-r--r--lib/isc/include/isc/resource.h87
-rw-r--r--lib/isc/include/isc/result.h293
-rw-r--r--lib/isc/include/isc/rwlock.h103
-rw-r--r--lib/isc/include/isc/safe.h44
-rw-r--r--lib/isc/include/isc/serial.h69
-rw-r--r--lib/isc/include/isc/siphash.h36
-rw-r--r--lib/isc/include/isc/sockaddr.h248
-rw-r--r--lib/isc/include/isc/stat.h44
-rw-r--r--lib/isc/include/isc/stats.h253
-rw-r--r--lib/isc/include/isc/stdatomic.h125
-rw-r--r--lib/isc/include/isc/stdio.h71
-rw-r--r--lib/isc/include/isc/stdtime.h61
-rw-r--r--lib/isc/include/isc/strerr.h29
-rw-r--r--lib/isc/include/isc/string.h42
-rw-r--r--lib/isc/include/isc/symtab.h135
-rw-r--r--lib/isc/include/isc/syslog.h38
-rw-r--r--lib/isc/include/isc/task.h646
-rw-r--r--lib/isc/include/isc/taskpool.h135
-rw-r--r--lib/isc/include/isc/thread.h53
-rw-r--r--lib/isc/include/isc/time.h468
-rw-r--r--lib/isc/include/isc/timer.h271
-rw-r--r--lib/isc/include/isc/tls.h587
-rw-r--r--lib/isc/include/isc/tm.h39
-rw-r--r--lib/isc/include/isc/types.h133
-rw-r--r--lib/isc/include/isc/url.h85
-rw-r--r--lib/isc/include/isc/utf8.h43
-rw-r--r--lib/isc/include/isc/util.h377
-rw-r--r--lib/isc/interfaceiter.c518
-rw-r--r--lib/isc/iterated_hash.c139
-rw-r--r--lib/isc/jemalloc_shim.h149
-rw-r--r--lib/isc/lex.c1136
-rw-r--r--lib/isc/lib.c55
-rw-r--r--lib/isc/log.c1909
-rw-r--r--lib/isc/managers.c117
-rw-r--r--lib/isc/md.c185
-rw-r--r--lib/isc/mem.c1908
-rw-r--r--lib/isc/mem_p.h36
-rw-r--r--lib/isc/meminfo.c50
-rw-r--r--lib/isc/mutex.c53
-rw-r--r--lib/isc/mutexblock.c35
-rw-r--r--lib/isc/net.c497
-rw-r--r--lib/isc/netaddr.c467
-rw-r--r--lib/isc/netmgr/http.c3755
-rw-r--r--lib/isc/netmgr/netmgr-int.h2273
-rw-r--r--lib/isc/netmgr/netmgr.c3991
-rw-r--r--lib/isc/netmgr/tcp.c1456
-rw-r--r--lib/isc/netmgr/tcpdns.c1500
-rw-r--r--lib/isc/netmgr/timer.c120
-rw-r--r--lib/isc/netmgr/tlsdns.c2363
-rw-r--r--lib/isc/netmgr/tlsstream.c1348
-rw-r--r--lib/isc/netmgr/udp.c1405
-rw-r--r--lib/isc/netmgr/uv-compat.c140
-rw-r--r--lib/isc/netmgr/uv-compat.h126
-rw-r--r--lib/isc/netmgr/uverr2result.c105
-rw-r--r--lib/isc/netmgr_p.h38
-rw-r--r--lib/isc/netscope.c76
-rw-r--r--lib/isc/nonce.c21
-rw-r--r--lib/isc/openssl_shim.c198
-rw-r--r--lib/isc/openssl_shim.h137
-rw-r--r--lib/isc/os.c113
-rw-r--r--lib/isc/os_p.h26
-rw-r--r--lib/isc/parseint.c79
-rw-r--r--lib/isc/picohttpparser.c727
-rw-r--r--lib/isc/picohttpparser.h100
-rw-r--r--lib/isc/pool.c163
-rw-r--r--lib/isc/portset.c135
-rw-r--r--lib/isc/quota.c202
-rw-r--r--lib/isc/radix.c706
-rw-r--r--lib/isc/random.c206
-rw-r--r--lib/isc/ratelimiter.c372
-rw-r--r--lib/isc/regex.c436
-rw-r--r--lib/isc/region.c39
-rw-r--r--lib/isc/resource.c218
-rw-r--r--lib/isc/result.c540
-rw-r--r--lib/isc/rwlock.c644
-rw-r--r--lib/isc/safe.c26
-rw-r--r--lib/isc/serial.c55
-rw-r--r--lib/isc/siphash.c235
-rw-r--r--lib/isc/sockaddr.c499
-rw-r--r--lib/isc/stats.c198
-rw-r--r--lib/isc/stdio.c152
-rw-r--r--lib/isc/stdtime.c63
-rw-r--r--lib/isc/string.c143
-rw-r--r--lib/isc/symtab.c294
-rw-r--r--lib/isc/syslog.c73
-rw-r--r--lib/isc/task.c1368
-rw-r--r--lib/isc/task_p.h106
-rw-r--r--lib/isc/taskpool.c157
-rw-r--r--lib/isc/thread.c121
-rw-r--r--lib/isc/time.c563
-rw-r--r--lib/isc/timer.c741
-rw-r--r--lib/isc/timer_p.h67
-rw-r--r--lib/isc/tls.c1678
-rw-r--r--lib/isc/tls_p.h20
-rw-r--r--lib/isc/tm.c469
-rw-r--r--lib/isc/trampoline.c194
-rw-r--r--lib/isc/trampoline_p.h90
-rw-r--r--lib/isc/url.c671
-rw-r--r--lib/isc/utf8.c89
-rw-r--r--lib/isccc/Makefile.am38
-rw-r--r--lib/isccc/Makefile.in956
-rw-r--r--lib/isccc/alist.c320
-rw-r--r--lib/isccc/base64.c74
-rw-r--r--lib/isccc/cc.c1057
-rw-r--r--lib/isccc/ccmsg.c185
-rw-r--r--lib/isccc/include/isccc/alist.h86
-rw-r--r--lib/isccc/include/isccc/base64.h79
-rw-r--r--lib/isccc/include/isccc/cc.h131
-rw-r--r--lib/isccc/include/isccc/ccmsg.h137
-rw-r--r--lib/isccc/include/isccc/events.h43
-rw-r--r--lib/isccc/include/isccc/sexpr.h119
-rw-r--r--lib/isccc/include/isccc/symtab.h133
-rw-r--r--lib/isccc/include/isccc/symtype.h37
-rw-r--r--lib/isccc/include/isccc/types.h52
-rw-r--r--lib/isccc/include/isccc/util.h208
-rw-r--r--lib/isccc/sexpr.c317
-rw-r--r--lib/isccc/symtab.c293
-rw-r--r--lib/isccfg/Makefile.am37
-rw-r--r--lib/isccfg/Makefile.in967
-rw-r--r--lib/isccfg/aclconf.c1010
-rw-r--r--lib/isccfg/dnsconf.c57
-rw-r--r--lib/isccfg/duration.c239
-rw-r--r--lib/isccfg/include/isccfg/aclconf.h91
-rw-r--r--lib/isccfg/include/isccfg/cfg.h609
-rw-r--r--lib/isccfg/include/isccfg/duration.h87
-rw-r--r--lib/isccfg/include/isccfg/grammar.h590
-rw-r--r--lib/isccfg/include/isccfg/kaspconf.h56
-rw-r--r--lib/isccfg/include/isccfg/log.h46
-rw-r--r--lib/isccfg/include/isccfg/namedconf.h54
-rw-r--r--lib/isccfg/kaspconf.c576
-rw-r--r--lib/isccfg/log.c38
-rw-r--r--lib/isccfg/namedconf.c3998
-rw-r--r--lib/isccfg/parser.c3901
-rw-r--r--lib/ns/Makefile.am57
-rw-r--r--lib/ns/Makefile.in1035
-rw-r--r--lib/ns/client.c3093
-rw-r--r--lib/ns/hooks.c363
-rw-r--r--lib/ns/include/ns/client.h578
-rw-r--r--lib/ns/include/ns/events.h25
-rw-r--r--lib/ns/include/ns/hooks.h612
-rw-r--r--lib/ns/include/ns/interfacemgr.h192
-rw-r--r--lib/ns/include/ns/listenlist.h137
-rw-r--r--lib/ns/include/ns/log.h72
-rw-r--r--lib/ns/include/ns/notify.h44
-rw-r--r--lib/ns/include/ns/query.h271
-rw-r--r--lib/ns/include/ns/server.h201
-rw-r--r--lib/ns/include/ns/sortlist.h80
-rw-r--r--lib/ns/include/ns/stats.h138
-rw-r--r--lib/ns/include/ns/types.h33
-rw-r--r--lib/ns/include/ns/update.h43
-rw-r--r--lib/ns/include/ns/xfrout.h30
-rw-r--r--lib/ns/interfacemgr.c1438
-rw-r--r--lib/ns/listenlist.c350
-rw-r--r--lib/ns/log.c62
-rw-r--r--lib/ns/notify.c180
-rw-r--r--lib/ns/query.c12446
-rw-r--r--lib/ns/server.c264
-rw-r--r--lib/ns/sortlist.c181
-rw-r--r--lib/ns/stats.c128
-rw-r--r--lib/ns/update.c3866
-rw-r--r--lib/ns/xfrout.c1829
639 files changed, 327028 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..39741a4
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,3 @@
+include $(top_srcdir)/Makefile.top
+
+SUBDIRS = isc dns isccc ns isccfg bind9 irs
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..9a6f85c
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,795 @@
+# 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
+
+subdir = lib
+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 $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+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 =
+SOURCES =
+DIST_SOURCES =
+RECURSIVE_TARGETS = all-recursive check-recursive cscopelist-recursive \
+ ctags-recursive dvi-recursive html-recursive info-recursive \
+ install-data-recursive install-dvi-recursive \
+ install-exec-recursive install-html-recursive \
+ install-info-recursive install-pdf-recursive \
+ install-ps-recursive install-recursive installcheck-recursive \
+ installdirs-recursive pdf-recursive ps-recursive \
+ tags-recursive uninstall-recursive
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \
+ distclean-recursive maintainer-clean-recursive
+am__recursive_targets = \
+ $(RECURSIVE_TARGETS) \
+ $(RECURSIVE_CLEAN_TARGETS) \
+ $(am__extra_recursive_targets)
+AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \
+ distdir distdir-am
+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)`
+DIST_SUBDIRS = $(SUBDIRS)
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+am__relativize = \
+ dir0=`pwd`; \
+ sed_first='s,^\([^/]*\)/.*$$,\1,'; \
+ sed_rest='s,^[^/]*/*,,'; \
+ sed_last='s,^.*/\([^/]*\)$$,\1,'; \
+ sed_butlast='s,/*[^/]*$$,,'; \
+ while test -n "$$dir1"; do \
+ first=`echo "$$dir1" | sed -e "$$sed_first"`; \
+ if test "$$first" != "."; then \
+ if test "$$first" = ".."; then \
+ dir2=`echo "$$dir0" | sed -e "$$sed_last"`/"$$dir2"; \
+ dir0=`echo "$$dir0" | sed -e "$$sed_butlast"`; \
+ else \
+ first2=`echo "$$dir2" | sed -e "$$sed_first"`; \
+ if test "$$first2" = "$$first"; then \
+ dir2=`echo "$$dir2" | sed -e "$$sed_rest"`; \
+ else \
+ dir2="../$$dir2"; \
+ fi; \
+ dir0="$$dir0"/"$$first"; \
+ fi; \
+ fi; \
+ dir1=`echo "$$dir1" | sed -e "$$sed_rest"`; \
+ done; \
+ reldir="$$dir2"
+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
+
+SUBDIRS = isc dns isccc ns isccfg bind9 irs
+all: all-recursive
+
+.SUFFIXES:
+$(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/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/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):
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+
+# This directory's subdirectories are mostly independent; you can cd
+# into them and run 'make' without going through this Makefile.
+# To change the values of 'make' variables: instead of editing Makefiles,
+# (1) if the variable is set in 'config.status', edit 'config.status'
+# (which will cause the Makefiles to be regenerated when you run 'make');
+# (2) otherwise, pass the desired values on the 'make' command line.
+$(am__recursive_targets):
+ @fail=; \
+ if $(am__make_keepgoing); then \
+ failcom='fail=yes'; \
+ else \
+ failcom='exit 1'; \
+ fi; \
+ dot_seen=no; \
+ target=`echo $@ | sed s/-recursive//`; \
+ case "$@" in \
+ distclean-* | maintainer-clean-*) list='$(DIST_SUBDIRS)' ;; \
+ *) list='$(SUBDIRS)' ;; \
+ esac; \
+ for subdir in $$list; do \
+ echo "Making $$target in $$subdir"; \
+ if test "$$subdir" = "."; then \
+ dot_seen=yes; \
+ local_target="$$target-am"; \
+ else \
+ local_target="$$target"; \
+ fi; \
+ ($(am__cd) $$subdir && $(MAKE) $(AM_MAKEFLAGS) $$local_target) \
+ || eval $$failcom; \
+ done; \
+ if test "$$dot_seen" = "no"; then \
+ $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
+ fi; test -z "$$fail"
+test-local:
+unit-local:
+doc-local:
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-recursive
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \
+ include_option=--etags-include; \
+ empty_fix=.; \
+ else \
+ include_option=--include; \
+ empty_fix=; \
+ fi; \
+ list='$(SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ test ! -f $$subdir/TAGS || \
+ set "$$@" "$$include_option=$$here/$$subdir/TAGS"; \
+ fi; \
+ done; \
+ $(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-recursive
+
+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-recursive
+
+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
+ @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
+ if test "$$subdir" = .; then :; else \
+ $(am__make_dryrun) \
+ || test -d "$(distdir)/$$subdir" \
+ || $(MKDIR_P) "$(distdir)/$$subdir" \
+ || exit 1; \
+ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \
+ $(am__relativize); \
+ new_distdir=$$reldir; \
+ dir1=$$subdir; dir2="$(top_distdir)"; \
+ $(am__relativize); \
+ new_top_distdir=$$reldir; \
+ echo " (cd $$subdir && $(MAKE) $(AM_MAKEFLAGS) top_distdir="$$new_top_distdir" distdir="$$new_distdir" \\"; \
+ echo " am__remove_distdir=: am__skip_length_check=: am__skip_mode_fix=: distdir)"; \
+ ($(am__cd) $$subdir && \
+ $(MAKE) $(AM_MAKEFLAGS) \
+ top_distdir="$$new_top_distdir" \
+ distdir="$$new_distdir" \
+ am__remove_distdir=: \
+ am__skip_length_check=: \
+ am__skip_mode_fix=: \
+ distdir) \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-recursive
+all-am: Makefile
+installdirs: installdirs-recursive
+installdirs-am:
+install: install-recursive
+install-exec: install-exec-recursive
+install-data: install-data-recursive
+uninstall: uninstall-recursive
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-recursive
+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:
+
+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."
+clean: clean-recursive
+
+clean-am: clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-recursive
+ -rm -f Makefile
+distclean-am: clean-am distclean-generic distclean-tags
+
+doc: doc-recursive
+
+doc-am: doc-local
+
+dvi: dvi-recursive
+
+dvi-am:
+
+html: html-recursive
+
+html-am:
+
+info: info-recursive
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-recursive
+
+install-dvi-am:
+
+install-exec-am:
+
+install-html: install-html-recursive
+
+install-html-am:
+
+install-info: install-info-recursive
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-recursive
+
+install-pdf-am:
+
+install-ps: install-ps-recursive
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-recursive
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-recursive
+
+mostlyclean-am: mostlyclean-generic mostlyclean-libtool
+
+pdf: pdf-recursive
+
+pdf-am:
+
+ps: ps-recursive
+
+ps-am:
+
+test: test-recursive
+
+test-am: test-local
+
+uninstall-am:
+
+unit: unit-recursive
+
+unit-am: unit-local
+
+.MAKE: $(am__recursive_targets) install-am install-strip
+
+.PHONY: $(am__recursive_targets) CTAGS GTAGS TAGS all all-am check \
+ check-am clean clean-generic clean-libtool cscopelist-am ctags \
+ ctags-am distclean 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-dvi install-dvi-am install-exec \
+ install-exec-am install-html install-html-am install-info \
+ install-info-am install-man install-pdf install-pdf-am \
+ install-ps install-ps-am install-strip installcheck \
+ installcheck-am installdirs installdirs-am maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am test-am \
+ test-local uninstall uninstall-am unit-am unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/bind9/Makefile.am b/lib/bind9/Makefile.am
new file mode 100644
index 0000000..7ec5bdd
--- /dev/null
+++ b/lib/bind9/Makefile.am
@@ -0,0 +1,36 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libbind9.la
+
+libbind9_ladir = $(includedir)/bind9
+libbind9_la_HEADERS = \
+ include/bind9/check.h \
+ include/bind9/getaddresses.h
+
+libbind9_la_SOURCES = \
+ $(libbind9_la_HEADERS) \
+ check.c \
+ getaddresses.c
+
+libbind9_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBNS_CFLAGS) \
+ $(LIBBIND9_CFLAGS) \
+ $(OPENSSL_CFLAGS)
+
+libbind9_la_LIBADD = \
+ $(LIBNS_LIBS) \
+ $(LIBISCCFG_LIBS) \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libbind9_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+if HAVE_DNSTAP
+libbind9_la_CPPFLAGS += $(DNSTAP_CFLAGS)
+endif
diff --git a/lib/bind9/Makefile.in b/lib/bind9/Makefile.in
new file mode 100644
index 0000000..cf12ed5
--- /dev/null
+++ b/lib/bind9/Makefile.in
@@ -0,0 +1,901 @@
+# 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_DNSTAP_TRUE@am__append_2 = $(DNSTAP_CFLAGS)
+subdir = lib/bind9
+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 $(libbind9_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)$(libbind9_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libbind9_la_DEPENDENCIES = $(LIBNS_LIBS) $(LIBISCCFG_LIBS) \
+ $(LIBDNS_LIBS) $(LIBISC_LIBS)
+am__objects_1 =
+am_libbind9_la_OBJECTS = $(am__objects_1) libbind9_la-check.lo \
+ libbind9_la-getaddresses.lo
+libbind9_la_OBJECTS = $(am_libbind9_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 =
+libbind9_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libbind9_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)/libbind9_la-check.Plo \
+ ./$(DEPDIR)/libbind9_la-getaddresses.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 = $(libbind9_la_SOURCES)
+DIST_SOURCES = $(libbind9_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libbind9_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 = libbind9.la
+libbind9_ladir = $(includedir)/bind9
+libbind9_la_HEADERS = \
+ include/bind9/check.h \
+ include/bind9/getaddresses.h
+
+libbind9_la_SOURCES = \
+ $(libbind9_la_HEADERS) \
+ check.c \
+ getaddresses.c
+
+libbind9_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) $(LIBISCCFG_CFLAGS) $(LIBNS_CFLAGS) \
+ $(LIBBIND9_CFLAGS) $(OPENSSL_CFLAGS) $(am__append_2)
+libbind9_la_LIBADD = \
+ $(LIBNS_LIBS) \
+ $(LIBISCCFG_LIBS) \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libbind9_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: 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/bind9/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/bind9/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}; \
+ }
+
+libbind9.la: $(libbind9_la_OBJECTS) $(libbind9_la_DEPENDENCIES) $(EXTRA_libbind9_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libbind9_la_LINK) -rpath $(libdir) $(libbind9_la_OBJECTS) $(libbind9_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbind9_la-check.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libbind9_la-getaddresses.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 $@ $<
+
+libbind9_la-check.lo: check.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbind9_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libbind9_la-check.lo -MD -MP -MF $(DEPDIR)/libbind9_la-check.Tpo -c -o libbind9_la-check.lo `test -f 'check.c' || echo '$(srcdir)/'`check.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbind9_la-check.Tpo $(DEPDIR)/libbind9_la-check.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='check.c' object='libbind9_la-check.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) $(libbind9_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libbind9_la-check.lo `test -f 'check.c' || echo '$(srcdir)/'`check.c
+
+libbind9_la-getaddresses.lo: getaddresses.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libbind9_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libbind9_la-getaddresses.lo -MD -MP -MF $(DEPDIR)/libbind9_la-getaddresses.Tpo -c -o libbind9_la-getaddresses.lo `test -f 'getaddresses.c' || echo '$(srcdir)/'`getaddresses.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libbind9_la-getaddresses.Tpo $(DEPDIR)/libbind9_la-getaddresses.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='getaddresses.c' object='libbind9_la-getaddresses.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) $(libbind9_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libbind9_la-getaddresses.lo `test -f 'getaddresses.c' || echo '$(srcdir)/'`getaddresses.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libbind9_laHEADERS: $(libbind9_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libbind9_la_HEADERS)'; test -n "$(libbind9_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libbind9_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libbind9_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)$(libbind9_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libbind9_ladir)" || exit $$?; \
+ done
+
+uninstall-libbind9_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libbind9_la_HEADERS)'; test -n "$(libbind9_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libbind9_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libbind9_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libbind9_la-check.Plo
+ -rm -f ./$(DEPDIR)/libbind9_la-getaddresses.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-libbind9_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)/libbind9_la-check.Plo
+ -rm -f ./$(DEPDIR)/libbind9_la-getaddresses.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-libLTLIBRARIES uninstall-libbind9_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libbind9_laHEADERS install-man 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-libLTLIBRARIES uninstall-libbind9_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/bind9/check.c b/lib/bind9/check.c
new file mode 100644
index 0000000..695090e
--- /dev/null
+++ b/lib/bind9/check.c
@@ -0,0 +1,6072 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <openssl/opensslv.h>
+
+#ifdef HAVE_DNSTAP
+#include <fstrm.h>
+#endif
+
+#include <isc/aes.h>
+#include <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/hex.h>
+#include <isc/log.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/siphash.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/symtab.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dnstap.h>
+#include <dns/fixedname.h>
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/peer.h>
+#include <dns/rbt.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatatype.h>
+#include <dns/rrl.h>
+#include <dns/secalg.h>
+#include <dns/ssu.h>
+
+#include <dst/dst.h>
+
+#include <isccfg/aclconf.h>
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#include <ns/hooks.h>
+
+#include <bind9/check.h>
+
+static in_port_t dnsport = 53;
+
+static isc_result_t
+fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable,
+ isc_log_t *logctxlogc);
+
+static isc_result_t
+keydirexist(const cfg_obj_t *zcgf, const char *dir, const char *kaspnamestr,
+ isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx);
+static void
+freekey(char *key, unsigned int type, isc_symvalue_t value, void *userarg) {
+ UNUSED(type);
+ UNUSED(value);
+ isc_mem_free(userarg, key);
+}
+
+static isc_result_t
+check_orderent(const cfg_obj_t *ent, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ isc_textregion_t r;
+ dns_fixedname_t fixed;
+ const cfg_obj_t *obj;
+ dns_rdataclass_t rdclass;
+ dns_rdatatype_t rdtype;
+ isc_buffer_t b;
+ const char *str;
+
+ dns_fixedname_init(&fixed);
+ obj = cfg_tuple_get(ent, "class");
+ if (cfg_obj_isstring(obj)) {
+ DE_CONST(cfg_obj_asstring(obj), r.base);
+ r.length = strlen(r.base);
+ tresult = dns_rdataclass_fromtext(&rdclass, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "rrset-order: invalid class '%s'", r.base);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ obj = cfg_tuple_get(ent, "type");
+ if (cfg_obj_isstring(obj)) {
+ DE_CONST(cfg_obj_asstring(obj), r.base);
+ r.length = strlen(r.base);
+ tresult = dns_rdatatype_fromtext(&rdtype, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "rrset-order: invalid type '%s'", r.base);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ obj = cfg_tuple_get(ent, "name");
+ if (cfg_obj_isstring(obj)) {
+ str = cfg_obj_asstring(obj);
+ isc_buffer_constinit(&b, str, strlen(str));
+ isc_buffer_add(&b, strlen(str));
+ tresult = dns_name_fromtext(dns_fixedname_name(&fixed), &b,
+ dns_rootname, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "rrset-order: invalid name '%s'", str);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ obj = cfg_tuple_get(ent, "order");
+ if (!cfg_obj_isstring(obj) ||
+ strcasecmp("order", cfg_obj_asstring(obj)) != 0)
+ {
+ cfg_obj_log(ent, logctx, ISC_LOG_ERROR,
+ "rrset-order: keyword 'order' missing");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = cfg_tuple_get(ent, "ordering");
+ if (!cfg_obj_isstring(obj)) {
+ cfg_obj_log(ent, logctx, ISC_LOG_ERROR,
+ "rrset-order: missing ordering");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ } else if (strcasecmp(cfg_obj_asstring(obj), "fixed") == 0) {
+#if !DNS_RDATASET_FIXED
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "rrset-order: order 'fixed' was disabled at "
+ "compilation time");
+#endif /* if !DNS_RDATASET_FIXED */
+ } else if (strcasecmp(cfg_obj_asstring(obj), "random") != 0 &&
+ strcasecmp(cfg_obj_asstring(obj), "cyclic") != 0 &&
+ strcasecmp(cfg_obj_asstring(obj), "none") != 0)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "rrset-order: invalid order '%s'",
+ cfg_obj_asstring(obj));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+check_order(const cfg_obj_t *options, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *element;
+ const cfg_obj_t *obj = NULL;
+
+ if (cfg_map_get(options, "rrset-order", &obj) != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ tresult = check_orderent(cfg_listelt_value(element), logctx);
+ if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+check_dual_stack(const cfg_obj_t *options, isc_log_t *logctx) {
+ const cfg_listelt_t *element;
+ const cfg_obj_t *alternates = NULL;
+ const cfg_obj_t *value;
+ const cfg_obj_t *obj;
+ const char *str;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ isc_buffer_t buffer;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+
+ (void)cfg_map_get(options, "dual-stack-servers", &alternates);
+
+ if (alternates == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ obj = cfg_tuple_get(alternates, "port");
+ if (cfg_obj_isuint32(obj)) {
+ uint32_t val = cfg_obj_asuint32(obj);
+ if (val > UINT16_MAX) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "port '%u' out of range", val);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+ obj = cfg_tuple_get(alternates, "addresses");
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ value = cfg_listelt_value(element);
+ if (cfg_obj_issockaddr(value)) {
+ continue;
+ }
+ obj = cfg_tuple_get(value, "name");
+ str = cfg_obj_asstring(obj);
+ isc_buffer_constinit(&buffer, str, strlen(str));
+ isc_buffer_add(&buffer, strlen(str));
+ name = dns_fixedname_initname(&fixed);
+ tresult = dns_name_fromtext(name, &buffer, dns_rootname, 0,
+ NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad name '%s'",
+ str);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ obj = cfg_tuple_get(value, "port");
+ if (cfg_obj_isuint32(obj)) {
+ uint32_t val = cfg_obj_asuint32(obj);
+ if (val > UINT16_MAX) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "port '%u' out of range", val);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+check_forward(const cfg_obj_t *options, const cfg_obj_t *global,
+ isc_log_t *logctx) {
+ const cfg_obj_t *forward = NULL;
+ const cfg_obj_t *forwarders = NULL;
+
+ (void)cfg_map_get(options, "forward", &forward);
+ (void)cfg_map_get(options, "forwarders", &forwarders);
+
+ if (forwarders != NULL && global != NULL) {
+ const char *file = cfg_obj_file(global);
+ unsigned int line = cfg_obj_line(global);
+ cfg_obj_log(forwarders, logctx, ISC_LOG_ERROR,
+ "forwarders declared in root zone and "
+ "in general configuration: %s:%u",
+ file, line);
+ return (ISC_R_FAILURE);
+ }
+ if (forward != NULL && forwarders == NULL) {
+ cfg_obj_log(forward, logctx, ISC_LOG_ERROR,
+ "no matching 'forwarders' statement");
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+disabled_algorithms(const cfg_obj_t *disabled, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *element;
+ const char *str;
+ isc_buffer_t b;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ const cfg_obj_t *obj;
+
+ name = dns_fixedname_initname(&fixed);
+ obj = cfg_tuple_get(disabled, "name");
+ str = cfg_obj_asstring(obj);
+ isc_buffer_constinit(&b, str, strlen(str));
+ isc_buffer_add(&b, strlen(str));
+ tresult = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad domain name '%s'",
+ str);
+ result = tresult;
+ }
+
+ obj = cfg_tuple_get(disabled, "algorithms");
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ isc_textregion_t r;
+ dns_secalg_t alg;
+
+ DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base);
+ r.length = strlen(r.base);
+
+ tresult = dns_secalg_fromtext(&alg, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(cfg_listelt_value(element), logctx,
+ ISC_LOG_ERROR, "invalid algorithm '%s'",
+ r.base);
+ result = tresult;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+disabled_ds_digests(const cfg_obj_t *disabled, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *element;
+ const char *str;
+ isc_buffer_t b;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ const cfg_obj_t *obj;
+
+ name = dns_fixedname_initname(&fixed);
+ obj = cfg_tuple_get(disabled, "name");
+ str = cfg_obj_asstring(obj);
+ isc_buffer_constinit(&b, str, strlen(str));
+ isc_buffer_add(&b, strlen(str));
+ tresult = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad domain name '%s'",
+ str);
+ result = tresult;
+ }
+
+ obj = cfg_tuple_get(disabled, "digests");
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ isc_textregion_t r;
+ dns_dsdigest_t digest;
+
+ DE_CONST(cfg_obj_asstring(cfg_listelt_value(element)), r.base);
+ r.length = strlen(r.base);
+
+ /* works with a numeric argument too */
+ tresult = dns_dsdigest_fromtext(&digest, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(cfg_listelt_value(element), logctx,
+ ISC_LOG_ERROR, "invalid digest type '%s'",
+ r.base);
+ result = tresult;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+nameexist(const cfg_obj_t *obj, const char *name, int value,
+ isc_symtab_t *symtab, const char *fmt, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ char *key;
+ const char *file;
+ unsigned int line;
+ isc_result_t result;
+ isc_symvalue_t symvalue;
+
+ key = isc_mem_strdup(mctx, name);
+ symvalue.as_cpointer = obj;
+ result = isc_symtab_define(symtab, key, value, symvalue,
+ isc_symexists_reject);
+ if (result == ISC_R_EXISTS) {
+ RUNTIME_CHECK(isc_symtab_lookup(symtab, key, value,
+ &symvalue) == ISC_R_SUCCESS);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, fmt, key, file, line);
+ isc_mem_free(mctx, key);
+ result = ISC_R_EXISTS;
+ } else if (result != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, key);
+ }
+ return (result);
+}
+
+static isc_result_t
+mustbesecure(const cfg_obj_t *secure, isc_symtab_t *symtab, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ const cfg_obj_t *obj;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ const char *str;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ isc_buffer_t b;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ name = dns_fixedname_initname(&fixed);
+ obj = cfg_tuple_get(secure, "name");
+ str = cfg_obj_asstring(obj);
+ isc_buffer_constinit(&b, str, strlen(str));
+ isc_buffer_add(&b, strlen(str));
+ result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "bad domain name '%s'",
+ str);
+ } else {
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ result = nameexist(secure, namebuf, 1, symtab,
+ "dnssec-must-be-secure '%s': already "
+ "exists previous definition: %s:%u",
+ logctx, mctx);
+ }
+ return (result);
+}
+
+static isc_result_t
+checkacl(const char *aclname, cfg_aclconfctx_t *actx, const cfg_obj_t *zconfig,
+ const cfg_obj_t *voptions, const cfg_obj_t *config, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result;
+ const cfg_obj_t *aclobj = NULL;
+ const cfg_obj_t *options;
+ dns_acl_t *acl = NULL;
+
+ if (zconfig != NULL) {
+ options = cfg_tuple_get(zconfig, "options");
+ cfg_map_get(options, aclname, &aclobj);
+ }
+ if (voptions != NULL && aclobj == NULL) {
+ cfg_map_get(voptions, aclname, &aclobj);
+ }
+ if (config != NULL && aclobj == NULL) {
+ options = NULL;
+ cfg_map_get(config, "options", &options);
+ if (options != NULL) {
+ cfg_map_get(options, aclname, &aclobj);
+ }
+ }
+ if (aclobj == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+ result = cfg_acl_fromconfig(aclobj, config, logctx, actx, mctx, 0,
+ &acl);
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+
+ if (strcasecmp(aclname, "allow-transfer") == 0 &&
+ cfg_obj_istuple(aclobj))
+ {
+ const cfg_obj_t *obj_port = cfg_tuple_get(
+ cfg_tuple_get(aclobj, "port-transport"), "port");
+ const cfg_obj_t *obj_proto = cfg_tuple_get(
+ cfg_tuple_get(aclobj, "port-transport"), "transport");
+
+ if (cfg_obj_isuint32(obj_port) &&
+ cfg_obj_asuint32(obj_port) >= UINT16_MAX)
+ {
+ cfg_obj_log(obj_port, logctx, ISC_LOG_ERROR,
+ "port value '%u' is out of range",
+
+ cfg_obj_asuint32(obj_port));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+
+ if (cfg_obj_isstring(obj_proto)) {
+ const char *allowed[] = { "tcp", "tls" };
+ const char *transport = cfg_obj_asstring(obj_proto);
+ bool found = false;
+ for (size_t i = 0; i < ARRAY_SIZE(allowed); i++) {
+ if (strcasecmp(transport, allowed[i]) == 0) {
+ found = true;
+ }
+ }
+
+ if (!found) {
+ cfg_obj_log(obj_proto, logctx, ISC_LOG_ERROR,
+ "'%s' is not a valid transport "
+ "protocol for "
+ "zone "
+ "transfers. Please specify either "
+ "'tcp' or 'tls'",
+ transport);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+check_viewacls(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions,
+ const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS, tresult;
+ int i = 0;
+
+ static const char *acls[] = {
+ "allow-query", "allow-query-on",
+ "allow-query-cache", "allow-query-cache-on",
+ "blackhole", "keep-response-order",
+ "match-clients", "match-destinations",
+ "sortlist", NULL
+ };
+
+ while (acls[i] != NULL) {
+ tresult = checkacl(acls[i++], actx, NULL, voptions, config,
+ logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ return (result);
+}
+
+static void
+dns64_error(const cfg_obj_t *obj, isc_log_t *logctx, isc_netaddr_t *netaddr,
+ unsigned int prefixlen, const char *message) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(netaddr, buf, sizeof(buf));
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, "dns64 prefix %s/%u %s", buf,
+ prefixlen, message);
+}
+
+static isc_result_t
+check_dns64(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions,
+ const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_obj_t *dns64 = NULL;
+ const cfg_obj_t *options;
+ const cfg_listelt_t *element;
+ const cfg_obj_t *map, *obj;
+ isc_netaddr_t na, sa;
+ unsigned int prefixlen;
+ int nbytes;
+ int i;
+
+ static const char *acls[] = { "clients", "exclude", "mapped", NULL };
+
+ if (voptions != NULL) {
+ cfg_map_get(voptions, "dns64", &dns64);
+ }
+ if (config != NULL && dns64 == NULL) {
+ options = NULL;
+ cfg_map_get(config, "options", &options);
+ if (options != NULL) {
+ cfg_map_get(options, "dns64", &dns64);
+ }
+ }
+ if (dns64 == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ for (element = cfg_list_first(dns64); element != NULL;
+ element = cfg_list_next(element))
+ {
+ map = cfg_listelt_value(element);
+ obj = cfg_map_getname(map);
+
+ cfg_obj_asnetprefix(obj, &na, &prefixlen);
+ if (na.family != AF_INET6) {
+ dns64_error(map, logctx, &na, prefixlen,
+ "must be IPv6");
+ result = ISC_R_FAILURE;
+ continue;
+ }
+
+ if (na.type.in6.s6_addr[8] != 0) {
+ dns64_error(map, logctx, &na, prefixlen,
+ "bits [64..71] must be zero");
+ result = ISC_R_FAILURE;
+ continue;
+ }
+
+ if (prefixlen != 32 && prefixlen != 40 && prefixlen != 48 &&
+ prefixlen != 56 && prefixlen != 64 && prefixlen != 96)
+ {
+ dns64_error(map, logctx, &na, prefixlen,
+ "length is not 32/40/48/56/64/96");
+ result = ISC_R_FAILURE;
+ continue;
+ }
+
+ for (i = 0; acls[i] != NULL; i++) {
+ obj = NULL;
+ (void)cfg_map_get(map, acls[i], &obj);
+ if (obj != NULL) {
+ dns_acl_t *acl = NULL;
+ isc_result_t tresult;
+
+ tresult = cfg_acl_fromconfig(obj, config,
+ logctx, actx, mctx,
+ 0, &acl);
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(map, "suffix", &obj);
+ if (obj != NULL) {
+ static const unsigned char zeros[16];
+ isc_netaddr_fromsockaddr(&sa, cfg_obj_assockaddr(obj));
+ if (sa.family != AF_INET6) {
+ cfg_obj_log(map, logctx, ISC_LOG_ERROR,
+ "dns64 requires a IPv6 suffix");
+ result = ISC_R_FAILURE;
+ continue;
+ }
+ nbytes = prefixlen / 8 + 4;
+ if (prefixlen <= 64) {
+ nbytes++;
+ }
+ if (memcmp(sa.type.in6.s6_addr, zeros, nbytes) != 0) {
+ char netaddrbuf[ISC_NETADDR_FORMATSIZE];
+ isc_netaddr_format(&sa, netaddrbuf,
+ sizeof(netaddrbuf));
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "bad suffix '%s' leading "
+ "%u octets not zeros",
+ netaddrbuf, nbytes);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ return (result);
+}
+
+#define CHECK_RRL(cond, pat, val1, val2) \
+ do { \
+ if (!(cond)) { \
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR, pat, val1, \
+ val2); \
+ if (result == ISC_R_SUCCESS) \
+ result = ISC_R_RANGE; \
+ } \
+ } while (0)
+
+#define CHECK_RRL_RATE(rate, def, max_rate, name) \
+ do { \
+ obj = NULL; \
+ mresult = cfg_map_get(map, name, &obj); \
+ if (mresult == ISC_R_SUCCESS) { \
+ rate = cfg_obj_asuint32(obj); \
+ CHECK_RRL(rate <= max_rate, name " %d > %d", rate, \
+ max_rate); \
+ } \
+ } while (0)
+
+static isc_result_t
+check_ratelimit(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions,
+ const cfg_obj_t *config, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t mresult;
+ const cfg_obj_t *map = NULL;
+ const cfg_obj_t *options;
+ const cfg_obj_t *obj;
+ int min_entries, i;
+ int all_per_second;
+ int errors_per_second;
+ int nodata_per_second;
+ int nxdomains_per_second;
+ int referrals_per_second;
+ int responses_per_second;
+ int slip;
+
+ if (voptions != NULL) {
+ cfg_map_get(voptions, "rate-limit", &map);
+ }
+ if (config != NULL && map == NULL) {
+ options = NULL;
+ cfg_map_get(config, "options", &options);
+ if (options != NULL) {
+ cfg_map_get(options, "rate-limit", &map);
+ }
+ }
+ if (map == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ min_entries = 500;
+ obj = NULL;
+ mresult = cfg_map_get(map, "min-table-size", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ min_entries = cfg_obj_asuint32(obj);
+ if (min_entries < 1) {
+ min_entries = 1;
+ }
+ }
+
+ obj = NULL;
+ mresult = cfg_map_get(map, "max-table-size", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ i = cfg_obj_asuint32(obj);
+ CHECK_RRL(i >= min_entries,
+ "max-table-size %d < min-table-size %d", i,
+ min_entries);
+ }
+
+ CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE,
+ "responses-per-second");
+
+ CHECK_RRL_RATE(referrals_per_second, responses_per_second,
+ DNS_RRL_MAX_RATE, "referrals-per-second");
+ CHECK_RRL_RATE(nodata_per_second, responses_per_second,
+ DNS_RRL_MAX_RATE, "nodata-per-second");
+ CHECK_RRL_RATE(nxdomains_per_second, responses_per_second,
+ DNS_RRL_MAX_RATE, "nxdomains-per-second");
+ CHECK_RRL_RATE(errors_per_second, responses_per_second,
+ DNS_RRL_MAX_RATE, "errors-per-second");
+
+ CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE, "all-per-second");
+
+ CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP, "slip");
+
+ obj = NULL;
+ mresult = cfg_map_get(map, "window", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ i = cfg_obj_asuint32(obj);
+ CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW,
+ "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
+ }
+
+ obj = NULL;
+ mresult = cfg_map_get(map, "qps-scale", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ i = cfg_obj_asuint32(obj);
+ CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, "");
+ }
+
+ obj = NULL;
+ mresult = cfg_map_get(map, "ipv4-prefix-length", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ i = cfg_obj_asuint32(obj);
+ CHECK_RRL(i >= 8 && i <= 32,
+ "invalid 'ipv4-prefix-length %d'%s", i, "");
+ }
+
+ obj = NULL;
+ mresult = cfg_map_get(map, "ipv6-prefix-length", &obj);
+ if (mresult == ISC_R_SUCCESS) {
+ i = cfg_obj_asuint32(obj);
+ CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX,
+ "ipv6-prefix-length %d < 16 or > %d", i,
+ DNS_RRL_MAX_PREFIX);
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(map, "exempt-clients", &obj);
+ if (obj != NULL) {
+ dns_acl_t *acl = NULL;
+ isc_result_t tresult;
+
+ tresult = cfg_acl_fromconfig(obj, config, logctx, actx, mctx, 0,
+ &acl);
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ return (result);
+}
+
+/*
+ * Check allow-recursion and allow-recursion-on acls, and also log a
+ * warning if they're inconsistent with the "recursion" option.
+ */
+static isc_result_t
+check_recursionacls(cfg_aclconfctx_t *actx, const cfg_obj_t *voptions,
+ const char *viewname, const cfg_obj_t *config,
+ isc_log_t *logctx, isc_mem_t *mctx) {
+ const cfg_obj_t *options, *aclobj, *obj = NULL;
+ dns_acl_t *acl = NULL;
+ isc_result_t result = ISC_R_SUCCESS, tresult;
+ bool recursion;
+ const char *forview = " for view ";
+ int i = 0;
+
+ static const char *acls[] = { "allow-recursion", "allow-recursion-on",
+ NULL };
+
+ if (voptions != NULL) {
+ cfg_map_get(voptions, "recursion", &obj);
+ }
+ if (obj == NULL && config != NULL) {
+ options = NULL;
+ cfg_map_get(config, "options", &options);
+ if (options != NULL) {
+ cfg_map_get(options, "recursion", &obj);
+ }
+ }
+ if (obj == NULL) {
+ recursion = true;
+ } else {
+ recursion = cfg_obj_asboolean(obj);
+ }
+
+ if (viewname == NULL) {
+ viewname = "";
+ forview = "";
+ }
+
+ for (i = 0; acls[i] != NULL; i++) {
+ aclobj = options = NULL;
+ acl = NULL;
+
+ if (voptions != NULL) {
+ cfg_map_get(voptions, acls[i], &aclobj);
+ }
+ if (config != NULL && aclobj == NULL) {
+ options = NULL;
+ cfg_map_get(config, "options", &options);
+ if (options != NULL) {
+ cfg_map_get(options, acls[i], &aclobj);
+ }
+ }
+ if (aclobj == NULL) {
+ continue;
+ }
+
+ tresult = cfg_acl_fromconfig(aclobj, config, logctx, actx, mctx,
+ 0, &acl);
+
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ if (acl == NULL) {
+ continue;
+ }
+
+ if (!recursion && !dns_acl_isnone(acl)) {
+ cfg_obj_log(aclobj, logctx, ISC_LOG_WARNING,
+ "both \"recursion no;\" and "
+ "\"%s\" active%s%s",
+ acls[i], forview, viewname);
+ }
+
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+ }
+
+ return (result);
+}
+
+typedef struct {
+ const char *name;
+ unsigned int scale;
+ unsigned int max;
+} intervaltable;
+
+#ifdef HAVE_DNSTAP
+typedef struct {
+ const char *name;
+ unsigned int min;
+ unsigned int max;
+} fstrmtable;
+#endif /* ifdef HAVE_DNSTAP */
+
+typedef enum {
+ optlevel_config,
+ optlevel_options,
+ optlevel_view,
+ optlevel_zone
+} optlevel_t;
+
+static isc_result_t
+check_name(const char *str) {
+ dns_fixedname_t fixed;
+
+ dns_fixedname_init(&fixed);
+ return (dns_name_fromstring(dns_fixedname_name(&fixed), str, 0, NULL));
+}
+
+static bool
+kasp_name_allowed(const cfg_listelt_t *element) {
+ const char *name = cfg_obj_asstring(
+ cfg_tuple_get(cfg_listelt_value(element), "name"));
+
+ if (strcmp("none", name) == 0) {
+ return (false);
+ }
+ if (strcmp("default", name) == 0) {
+ return (false);
+ }
+ if (strcmp("insecure", name) == 0) {
+ return (false);
+ }
+ return (true);
+}
+
+static const cfg_obj_t *
+find_maplist(const cfg_obj_t *config, const char *listname, const char *name) {
+ isc_result_t result;
+ const cfg_obj_t *maplist = NULL;
+ const cfg_listelt_t *elt = NULL;
+
+ REQUIRE(config != NULL);
+ REQUIRE(name != NULL);
+
+ result = cfg_map_get(config, listname, &maplist);
+ if (result != ISC_R_SUCCESS) {
+ return (NULL);
+ }
+
+ for (elt = cfg_list_first(maplist); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *map = cfg_listelt_value(elt);
+ if (strcasecmp(cfg_obj_asstring(cfg_map_getname(map)), name) ==
+ 0)
+ {
+ return (map);
+ }
+ }
+
+ return (NULL);
+}
+
+static isc_result_t
+check_listener(const cfg_obj_t *listener, const cfg_obj_t *config,
+ cfg_aclconfctx_t *actx, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t tresult, result = ISC_R_SUCCESS;
+ const cfg_obj_t *ltup = NULL;
+ const cfg_obj_t *tlsobj = NULL, *httpobj = NULL;
+ const cfg_obj_t *portobj = NULL;
+ const cfg_obj_t *http_server = NULL;
+ bool do_tls = false, no_tls = false;
+ dns_acl_t *acl = NULL;
+
+ ltup = cfg_tuple_get(listener, "tuple");
+ RUNTIME_CHECK(ltup != NULL);
+
+ tlsobj = cfg_tuple_get(ltup, "tls");
+ if (tlsobj != NULL && cfg_obj_isstring(tlsobj)) {
+ const char *tlsname = cfg_obj_asstring(tlsobj);
+
+ if (strcasecmp(tlsname, "none") == 0) {
+ no_tls = true;
+ } else if (strcasecmp(tlsname, "ephemeral") == 0) {
+ do_tls = true;
+ } else {
+ const cfg_obj_t *tlsmap = NULL;
+
+ do_tls = true;
+
+ tlsmap = find_maplist(config, "tls", tlsname);
+ if (tlsmap == NULL) {
+ cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR,
+ "tls '%s' is not defined",
+ cfg_obj_asstring(tlsobj));
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ httpobj = cfg_tuple_get(ltup, "http");
+ if (httpobj != NULL && cfg_obj_isstring(httpobj)) {
+ const char *httpname = cfg_obj_asstring(httpobj);
+
+ if (!do_tls && !no_tls) {
+ cfg_obj_log(httpobj, logctx, ISC_LOG_ERROR,
+ "http must specify a 'tls' "
+ "statement, 'tls ephemeral', or "
+ "'tls none'");
+ result = ISC_R_FAILURE;
+ }
+
+ http_server = find_maplist(config, "http", httpname);
+ if (http_server == NULL && strcasecmp(httpname, "default") != 0)
+ {
+ cfg_obj_log(httpobj, logctx, ISC_LOG_ERROR,
+ "http '%s' is not defined",
+ cfg_obj_asstring(httpobj));
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ portobj = cfg_tuple_get(ltup, "port");
+ if (cfg_obj_isuint32(portobj) &&
+ cfg_obj_asuint32(portobj) >= UINT16_MAX)
+ {
+ cfg_obj_log(portobj, logctx, ISC_LOG_ERROR,
+ "port value '%u' is out of range",
+
+ cfg_obj_asuint32(portobj));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+
+ tresult = cfg_acl_fromconfig(cfg_tuple_get(listener, "acl"), config,
+ logctx, actx, mctx, 0, &acl);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+check_listeners(const cfg_obj_t *list, const cfg_obj_t *config,
+ cfg_aclconfctx_t *actx, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t tresult, result = ISC_R_SUCCESS;
+ const cfg_listelt_t *elt = NULL;
+
+ for (elt = cfg_list_first(list); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *obj = cfg_listelt_value(elt);
+ tresult = check_listener(obj, config, actx, logctx, mctx);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+check_port(const cfg_obj_t *options, isc_log_t *logctx, const char *type,
+ in_port_t *portp) {
+ const cfg_obj_t *portobj = NULL;
+ isc_result_t result;
+
+ result = cfg_map_get(options, type, &portobj);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (cfg_obj_asuint32(portobj) >= UINT16_MAX) {
+ cfg_obj_log(portobj, logctx, ISC_LOG_ERROR,
+ "port '%u' out of range",
+ cfg_obj_asuint32(portobj));
+ return (ISC_R_RANGE);
+ }
+
+ if (portp != NULL) {
+ *portp = (in_port_t)cfg_obj_asuint32(portobj);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_options(const cfg_obj_t *options, const cfg_obj_t *config,
+ isc_log_t *logctx, isc_mem_t *mctx, optlevel_t optlevel) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ unsigned int i;
+ const cfg_obj_t *obj = NULL;
+ const cfg_obj_t *resignobj = NULL;
+ const cfg_listelt_t *element;
+ isc_symtab_t *symtab = NULL;
+ const char *str;
+ isc_buffer_t b;
+ uint32_t lifetime = 3600;
+ bool has_dnssecpolicy = false;
+ const char *ccalg = "siphash24";
+ cfg_aclconfctx_t *actx = NULL;
+ static const char *sources[] = {
+ "query-source",
+ "query-source-v6",
+ };
+
+ /*
+ * { "name", scale, value }
+ * (scale * value) <= UINT32_MAX
+ */
+ static intervaltable intervals[] = {
+ { "heartbeat-interval", 60, 28 * 24 * 60 }, /* 28 days */
+ { "interface-interval", 60, 28 * 24 * 60 }, /* 28 days */
+ { "max-transfer-idle-in", 60, 28 * 24 * 60 }, /* 28 days */
+ { "max-transfer-idle-out", 60, 28 * 24 * 60 }, /* 28 days */
+ { "max-transfer-time-in", 60, 28 * 24 * 60 }, /* 28 days */
+ { "max-transfer-time-out", 60, 28 * 24 * 60 }, /* 28 days */
+
+ /* minimum and maximum cache and negative cache TTLs */
+ { "min-cache-ttl", 1, MAX_MIN_CACHE_TTL }, /* 90 secs */
+ { "max-cache-ttl", 1, UINT32_MAX }, /* no limit */
+ { "min-ncache-ttl", 1, MAX_MIN_NCACHE_TTL }, /* 90 secs */
+ { "max-ncache-ttl", 1, MAX_MAX_NCACHE_TTL }, /* 7 days */
+ };
+
+ static const char *server_contact[] = { "empty-server", "empty-contact",
+ "dns64-server", "dns64-contact",
+ NULL };
+
+#ifdef HAVE_DNSTAP
+ static fstrmtable fstrm[] = {
+ { "fstrm-set-buffer-hint", FSTRM_IOTHR_BUFFER_HINT_MIN,
+ FSTRM_IOTHR_BUFFER_HINT_MAX },
+ { "fstrm-set-flush-timeout", FSTRM_IOTHR_FLUSH_TIMEOUT_MIN,
+ FSTRM_IOTHR_FLUSH_TIMEOUT_MAX },
+ { "fstrm-set-input-queue-size",
+ FSTRM_IOTHR_INPUT_QUEUE_SIZE_MIN,
+ FSTRM_IOTHR_INPUT_QUEUE_SIZE_MAX },
+ { "fstrm-set-output-notify-threshold",
+ FSTRM_IOTHR_QUEUE_NOTIFY_THRESHOLD_MIN, 0 },
+ { "fstrm-set-output-queue-size",
+ FSTRM_IOTHR_OUTPUT_QUEUE_SIZE_MIN,
+ FSTRM_IOTHR_OUTPUT_QUEUE_SIZE_MAX },
+ { "fstrm-set-reopen-interval", FSTRM_IOTHR_REOPEN_INTERVAL_MIN,
+ FSTRM_IOTHR_REOPEN_INTERVAL_MAX }
+ };
+#endif /* ifdef HAVE_DNSTAP */
+
+ if (optlevel == optlevel_options) {
+ /*
+ * Check port values, and record "port" for later use.
+ */
+ tresult = check_port(options, logctx, "port", &dnsport);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ tresult = check_port(options, logctx, "tls-port", NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ tresult = check_port(options, logctx, "http-port", NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ tresult = check_port(options, logctx, "https-port", NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ if (optlevel == optlevel_options || optlevel == optlevel_view) {
+ /*
+ * Warn if query-source or query-source-v6 options specify
+ * a port, and fail if they specify the DNS port.
+ */
+ for (i = 0; i < ARRAY_SIZE(sources); i++) {
+ obj = NULL;
+ (void)cfg_map_get(options, sources[i], &obj);
+ if (obj != NULL) {
+ const isc_sockaddr_t *sa =
+ cfg_obj_assockaddr(obj);
+ in_port_t port = isc_sockaddr_getport(sa);
+ if (port == dnsport) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'%s' cannot specify the "
+ "DNS listener port (%d)",
+ sources[i], port);
+ result = ISC_R_FAILURE;
+ } else if (port != 0) {
+ cfg_obj_log(obj, logctx,
+ ISC_LOG_WARNING,
+ "'%s': specifying a port "
+ "is not recommended",
+ sources[i]);
+ }
+ }
+ }
+ }
+
+ /*
+ * Check that fields specified in units of time other than seconds
+ * have reasonable values.
+ */
+ for (i = 0; i < sizeof(intervals) / sizeof(intervals[0]); i++) {
+ uint32_t val;
+ obj = NULL;
+ (void)cfg_map_get(options, intervals[i].name, &obj);
+ if (obj == NULL) {
+ continue;
+ }
+ if (cfg_obj_isduration(obj)) {
+ val = cfg_obj_asduration(obj);
+ } else {
+ val = cfg_obj_asuint32(obj);
+ }
+ if (val > intervals[i].max) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' is out of range (0..%u)",
+ intervals[i].name, val, intervals[i].max);
+ result = ISC_R_RANGE;
+ } else if (val > (UINT32_MAX / intervals[i].scale)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%d' is out of range",
+ intervals[i].name, val);
+ result = ISC_R_RANGE;
+ }
+ }
+
+ /*
+ * Check dnssec-policy.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "dnssec-policy", &obj);
+ if (obj != NULL) {
+ bool bad_kasp = false;
+ bool bad_name = false;
+
+ if (optlevel != optlevel_config && !cfg_obj_isstring(obj)) {
+ bad_kasp = true;
+ } else if (optlevel == optlevel_config) {
+ dns_kasplist_t list;
+ dns_kasp_t *kasp = NULL, *kasp_next = NULL;
+
+ ISC_LIST_INIT(list);
+
+ if (cfg_obj_islist(obj)) {
+ for (element = cfg_list_first(obj);
+ element != NULL;
+ element = cfg_list_next(element))
+ {
+ isc_result_t ret;
+ cfg_obj_t *kconfig =
+ cfg_listelt_value(element);
+
+ if (!cfg_obj_istuple(kconfig)) {
+ bad_kasp = true;
+ continue;
+ }
+ if (!kasp_name_allowed(element)) {
+ bad_name = true;
+ continue;
+ }
+
+ ret = cfg_kasp_fromconfig(kconfig, NULL,
+ mctx, logctx,
+ &list, &kasp);
+ if (ret != ISC_R_SUCCESS) {
+ if (result == ISC_R_SUCCESS) {
+ result = ret;
+ }
+ }
+
+ if (kasp != NULL) {
+ dns_kasp_detach(&kasp);
+ }
+ }
+ }
+
+ for (kasp = ISC_LIST_HEAD(list); kasp != NULL;
+ kasp = kasp_next)
+ {
+ kasp_next = ISC_LIST_NEXT(kasp, link);
+ ISC_LIST_UNLINK(list, kasp, link);
+ dns_kasp_detach(&kasp);
+ }
+ }
+
+ if (bad_kasp) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy may only be configured at "
+ "the top level, please use name reference "
+ "at the zone level");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ } else if (bad_name) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy name may not be 'insecure', "
+ "'none', or 'default' (which are built-in "
+ "policies)");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ } else {
+ has_dnssecpolicy = true;
+ }
+ }
+
+ obj = NULL;
+ cfg_map_get(options, "max-rsa-exponent-size", &obj);
+ if (obj != NULL) {
+ uint32_t val;
+
+ val = cfg_obj_asuint32(obj);
+ if (val != 0 && (val < 35 || val > 4096)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "max-rsa-exponent-size '%u' is out of "
+ "range (35..4096)",
+ val);
+ result = ISC_R_RANGE;
+ }
+ }
+
+ obj = NULL;
+ cfg_map_get(options, "sig-validity-interval", &obj);
+ if (obj != NULL) {
+ uint32_t validity, resign = 0;
+
+ validity = cfg_obj_asuint32(cfg_tuple_get(obj, "validity"));
+ resignobj = cfg_tuple_get(obj, "re-sign");
+ if (!cfg_obj_isvoid(resignobj)) {
+ resign = cfg_obj_asuint32(resignobj);
+ }
+
+ if (validity > 3660 || validity == 0) { /* 10 years */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' is out of range (1..3660)",
+ "sig-validity-interval", validity);
+ result = ISC_R_RANGE;
+ }
+
+ if (!cfg_obj_isvoid(resignobj)) {
+ if (resign > 3660 || resign == 0) { /* 10 years */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' is out of range (1..3660)",
+ "sig-validity-interval (re-sign)",
+ validity);
+ result = ISC_R_RANGE;
+ } else if ((validity > 7 && validity < resign) ||
+ (validity <= 7 && validity * 24 < resign))
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "validity interval (%u days) "
+ "less than re-signing interval "
+ "(%u %s)",
+ validity, resign,
+ (validity > 7) ? "days" : "hours");
+ result = ISC_R_RANGE;
+ }
+ }
+
+ if (has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "sig-validity-interval: cannot be "
+ "configured if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ cfg_map_get(options, "dnskey-sig-validity", &obj);
+ if (obj != NULL) {
+ uint32_t keyvalidity;
+
+ keyvalidity = cfg_obj_asuint32(obj);
+ if (keyvalidity > 3660) { /* 10 years */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' is out of range (0..3660)",
+ "dnskey-sig-validity", keyvalidity);
+ result = ISC_R_RANGE;
+ }
+
+ if (has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnskey-sig-validity: cannot be "
+ "configured if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "preferred-glue", &obj);
+ if (obj != NULL) {
+ str = cfg_obj_asstring(obj);
+ if (strcasecmp(str, "a") != 0 && strcasecmp(str, "aaaa") != 0 &&
+ strcasecmp(str, "none") != 0)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "preferred-glue unexpected value '%s'",
+ str);
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "root-delegation-only", &obj);
+ if (obj != NULL) {
+ if (!cfg_obj_isvoid(obj)) {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *exclude;
+
+ exclude = cfg_listelt_value(element);
+ str = cfg_obj_asstring(exclude);
+ tresult = check_name(str);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "bad domain name '%s'",
+ str);
+ result = tresult;
+ }
+ }
+ }
+ }
+
+ /*
+ * Set supported DNSSEC algorithms.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "disable-algorithms", &obj);
+ if (obj != NULL) {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ tresult = disabled_algorithms(obj, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ /*
+ * Set supported DS digest types.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "disable-ds-digests", &obj);
+ if (obj != NULL) {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ tresult = disabled_ds_digests(obj, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ /*
+ * Check auto-dnssec at the view/options level
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "auto-dnssec", &obj);
+ if (obj != NULL) {
+ const char *arg = cfg_obj_asstring(obj);
+ if (optlevel != optlevel_zone && strcasecmp(arg, "off") != 0) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "auto-dnssec may only be activated at the "
+ "zone level");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Check dnssec-must-be-secure.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "dnssec-must-be-secure", &obj);
+ if (obj != NULL) {
+ tresult = isc_symtab_create(mctx, 100, freekey, mctx, false,
+ &symtab);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ } else {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ tresult = mustbesecure(obj, symtab, logctx,
+ mctx);
+ if (result == ISC_R_SUCCESS &&
+ tresult != ISC_R_SUCCESS)
+ {
+ result = tresult;
+ }
+ }
+ }
+ if (symtab != NULL) {
+ isc_symtab_destroy(&symtab);
+ }
+ }
+
+ /*
+ * Check server/contacts for syntactic validity.
+ */
+ for (i = 0; server_contact[i] != NULL; i++) {
+ obj = NULL;
+ (void)cfg_map_get(options, server_contact[i], &obj);
+ if (obj != NULL) {
+ str = cfg_obj_asstring(obj);
+ if (check_name(str) != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s: invalid name '%s'",
+ server_contact[i], str);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check empty zone configuration.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "disable-empty-zone", &obj);
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ str = cfg_obj_asstring(obj);
+ if (check_name(str) != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "disable-empty-zone: invalid name '%s'",
+ str);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Check that server-id is not too long.
+ * 1024 bytes should be big enough.
+ */
+ obj = NULL;
+ (void)cfg_map_get(options, "server-id", &obj);
+ if (obj != NULL && cfg_obj_isstring(obj) &&
+ strlen(cfg_obj_asstring(obj)) > 1024U)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'server-id' too big (>1024 bytes)");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "nta-lifetime", &obj);
+ if (obj != NULL) {
+ lifetime = cfg_obj_asduration(obj);
+ if (lifetime > 604800) { /* 7 days */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'nta-lifetime' cannot exceed one week");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ } else if (lifetime == 0) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'nta-lifetime' may not be zero");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "nta-recheck", &obj);
+ if (obj != NULL) {
+ uint32_t recheck = cfg_obj_asduration(obj);
+ if (recheck > 604800) { /* 7 days */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'nta-recheck' cannot exceed one week");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+
+ if (recheck > lifetime) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'nta-recheck' (%d seconds) is "
+ "greater than 'nta-lifetime' "
+ "(%d seconds)",
+ recheck, lifetime);
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "cookie-algorithm", &obj);
+ if (obj != NULL) {
+ ccalg = cfg_obj_asstring(obj);
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "cookie-secret", &obj);
+ if (obj != NULL) {
+ unsigned char secret[32];
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ unsigned int usedlength;
+
+ obj = cfg_listelt_value(element);
+ str = cfg_obj_asstring(obj);
+
+ memset(secret, 0, sizeof(secret));
+ isc_buffer_init(&b, secret, sizeof(secret));
+ tresult = isc_hex_decodestring(str, &b);
+ if (tresult == ISC_R_NOSPACE) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "cookie-secret: too long");
+ } else if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "cookie-secret: invalid hex "
+ "string");
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ continue;
+ }
+
+ usedlength = isc_buffer_usedlength(&b);
+ if (strcasecmp(ccalg, "aes") == 0 &&
+ usedlength != ISC_AES128_KEYLENGTH)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "AES cookie-secret must be 128 "
+ "bits");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ if (strcasecmp(ccalg, "siphash24") == 0 &&
+ usedlength != ISC_SIPHASH24_KEY_LENGTH)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "SipHash-2-4 cookie-secret must be "
+ "128 bits");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+ }
+
+#ifdef HAVE_DNSTAP
+ for (i = 0; i < sizeof(fstrm) / sizeof(fstrm[0]); i++) {
+ uint32_t value;
+
+ obj = NULL;
+ (void)cfg_map_get(options, fstrm[i].name, &obj);
+ if (obj == NULL) {
+ continue;
+ }
+
+ if (cfg_obj_isduration(obj)) {
+ value = cfg_obj_asduration(obj);
+ } else {
+ value = cfg_obj_asuint32(obj);
+ }
+ if (value < fstrm[i].min ||
+ (fstrm[i].max != 0U && value > fstrm[i].max))
+ {
+ if (fstrm[i].max != 0U) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' out of range (%u..%u)",
+ fstrm[i].name, value, fstrm[i].min,
+ fstrm[i].max);
+ } else {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s out of range (%u < %u)",
+ fstrm[i].name, value, fstrm[i].min);
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+
+ if (strcmp(fstrm[i].name, "fstrm-set-input-queue-size") == 0) {
+ int bits = 0;
+ do {
+ bits += value & 0x1;
+ value >>= 1;
+ } while (value != 0U);
+ if (bits != 1) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s '%u' not a power-of-2",
+ fstrm[i].name,
+ cfg_obj_asuint32(obj));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+ }
+
+ /* Check that dnstap-ouput values are consistent */
+ obj = NULL;
+ (void)cfg_map_get(options, "dnstap-output", &obj);
+ if (obj != NULL) {
+ const cfg_obj_t *obj2;
+ dns_dtmode_t dmode;
+
+ obj2 = cfg_tuple_get(obj, "mode");
+ if (obj2 == NULL) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnstap-output mode not found");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ } else {
+ if (strcasecmp(cfg_obj_asstring(obj2), "file") == 0) {
+ dmode = dns_dtmode_file;
+ } else {
+ dmode = dns_dtmode_unix;
+ }
+
+ obj2 = cfg_tuple_get(obj, "size");
+ if (obj2 != NULL && !cfg_obj_isvoid(obj2) &&
+ dmode == dns_dtmode_unix)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnstap-output size "
+ "cannot be set with mode unix");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj2 = cfg_tuple_get(obj, "versions");
+ if (obj2 != NULL && !cfg_obj_isvoid(obj2) &&
+ dmode == dns_dtmode_unix)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnstap-output versions "
+ "cannot be set with mode unix");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj2 = cfg_tuple_get(obj, "suffix");
+ if (obj2 != NULL && !cfg_obj_isvoid(obj2) &&
+ dmode == dns_dtmode_unix)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnstap-output suffix "
+ "cannot be set with mode unix");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ }
+#endif /* ifdef HAVE_DNSTAP */
+
+ obj = NULL;
+ (void)cfg_map_get(options, "lmdb-mapsize", &obj);
+ if (obj != NULL) {
+ uint64_t mapsize = cfg_obj_asuint64(obj);
+
+ if (mapsize < (1ULL << 20)) { /* 1 megabyte */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'lmdb-mapsize "
+ "%" PRId64 "' "
+ "is too small",
+ mapsize);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ } else if (mapsize > (1ULL << 40)) { /* 1 terabyte */
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'lmdb-mapsize "
+ "%" PRId64 "' "
+ "is too large",
+ mapsize);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "resolver-nonbackoff-tries", &obj);
+ if (obj != NULL && cfg_obj_asuint32(obj) == 0U) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'resolver-nonbackoff-tries' must be >= 1");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "max-ixfr-ratio", &obj);
+ if (obj != NULL && cfg_obj_ispercentage(obj)) {
+ uint32_t percent = cfg_obj_aspercentage(obj);
+ if (percent == 0) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'ixfr-max-ratio' must be a nonzero "
+ "percentage or 'unlimited')");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_RANGE;
+ }
+ } else if (percent > 100) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'ixfr-max-ratio %d%%' exceeds 100%%",
+ percent);
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "check-names", &obj);
+ if (obj != NULL && !cfg_obj_islist(obj)) {
+ obj = NULL;
+ }
+ if (obj != NULL) {
+ /* Note: SEC is defined in <sys/time.h> on some platforms. */
+ enum { MAS = 1, PRI = 2, SLA = 4, SCN = 8 } values = 0;
+ for (const cfg_listelt_t *el = cfg_list_first(obj); el != NULL;
+ el = cfg_list_next(el))
+ {
+ const cfg_obj_t *tuple = cfg_listelt_value(el);
+ const cfg_obj_t *type = cfg_tuple_get(tuple, "type");
+ const char *keyword = cfg_obj_asstring(type);
+ if (strcasecmp(keyword, "primary") == 0) {
+ if ((values & PRI) == PRI) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names primary' "
+ "duplicated");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ values |= PRI;
+ } else if (strcasecmp(keyword, "master") == 0) {
+ if ((values & MAS) == MAS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names master' "
+ "duplicated");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ values |= MAS;
+ } else if (strcasecmp(keyword, "secondary") == 0) {
+ if ((values & SCN) == SCN) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names secondary' "
+ "duplicated");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ values |= SCN;
+ } else if (strcasecmp(keyword, "slave") == 0) {
+ if ((values & SLA) == SLA) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names slave' "
+ "duplicated");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ values |= SLA;
+ }
+ }
+
+ if ((values & (PRI | MAS)) == (PRI | MAS)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names' cannot take both "
+ "'primary' and 'master'");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ if ((values & (SCN | SLA)) == (SCN | SLA)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'check-names' cannot take both "
+ "'secondary' and 'slave'");
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "stale-refresh-time", &obj);
+ if (obj != NULL) {
+ uint32_t refresh_time = cfg_obj_asduration(obj);
+ if (refresh_time > 0 && refresh_time < 30) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'stale-refresh-time' should either be 0 "
+ "or otherwise 30 seconds or higher");
+ }
+ }
+
+ cfg_aclconfctx_create(mctx, &actx);
+
+ obj = NULL;
+ (void)cfg_map_get(options, "listen-on", &obj);
+ if (obj != NULL) {
+ INSIST(config != NULL);
+ tresult = check_listeners(obj, config, actx, logctx, mctx);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ obj = NULL;
+ (void)cfg_map_get(options, "listen-on-v6", &obj);
+ if (obj != NULL) {
+ INSIST(config != NULL);
+ tresult = check_listeners(obj, config, actx, logctx, mctx);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ if (actx != NULL) {
+ cfg_aclconfctx_detach(&actx);
+ }
+
+ return (result);
+}
+
+/*
+ * Check "remote-servers" style list.
+ */
+static isc_result_t
+bind9_check_remoteserverlist(const cfg_obj_t *cctx, const char *list,
+ isc_log_t *logctx, isc_symtab_t *symtab,
+ isc_mem_t *mctx) {
+ isc_symvalue_t symvalue;
+ isc_result_t result, tresult;
+ const cfg_obj_t *obj = NULL;
+ const cfg_listelt_t *elt;
+
+ result = cfg_map_get(cctx, list, &obj);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ elt = cfg_list_first(obj);
+ while (elt != NULL) {
+ char *tmp;
+ const char *name;
+
+ obj = cfg_listelt_value(elt);
+ name = cfg_obj_asstring(cfg_tuple_get(obj, "name"));
+
+ tmp = isc_mem_strdup(mctx, name);
+ symvalue.as_cpointer = obj;
+ tresult = isc_symtab_define(symtab, tmp, 1, symvalue,
+ isc_symexists_reject);
+ if (tresult == ISC_R_EXISTS) {
+ const char *file = NULL;
+ unsigned int line;
+
+ RUNTIME_CHECK(
+ isc_symtab_lookup(symtab, tmp, 1, &symvalue) ==
+ ISC_R_SUCCESS);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "%s list '%s' is duplicated: "
+ "also defined at %s:%u",
+ list, name, file, line);
+ isc_mem_free(mctx, tmp);
+ result = tresult;
+ break;
+ } else if (tresult != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, tmp);
+ result = tresult;
+ break;
+ }
+
+ elt = cfg_list_next(elt);
+ }
+ return (result);
+}
+
+/*
+ * Check primaries lists for duplicates.
+ */
+static isc_result_t
+bind9_check_primarylists(const cfg_obj_t *cctx, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result, tresult;
+ isc_symtab_t *symtab = NULL;
+
+ result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ tresult = bind9_check_remoteserverlist(cctx, "primaries", logctx,
+ symtab, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ tresult = bind9_check_remoteserverlist(cctx, "masters", logctx, symtab,
+ mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ isc_symtab_destroy(&symtab);
+ return (result);
+}
+
+/*
+ * Check parental-agents lists for duplicates.
+ */
+static isc_result_t
+bind9_check_parentalagentlists(const cfg_obj_t *cctx, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result, tresult;
+ isc_symtab_t *symtab = NULL;
+
+ result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ tresult = bind9_check_remoteserverlist(cctx, "parental-agents", logctx,
+ symtab, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ isc_symtab_destroy(&symtab);
+ return (result);
+}
+
+#if HAVE_LIBNGHTTP2
+static isc_result_t
+bind9_check_httpserver(const cfg_obj_t *http, isc_log_t *logctx,
+ isc_symtab_t *symtab) {
+ isc_result_t result, tresult;
+ const char *name = cfg_obj_asstring(cfg_map_getname(http));
+ const cfg_obj_t *eps = NULL;
+ const cfg_listelt_t *elt = NULL;
+ isc_symvalue_t symvalue;
+
+ if (strcasecmp(name, "default") == 0) {
+ cfg_obj_log(http, logctx, ISC_LOG_ERROR,
+ "'http' name cannot be '%s' (which is a "
+ "built-in configuration)",
+ name);
+ result = ISC_R_FAILURE;
+ } else {
+ /* Check for duplicates */
+ symvalue.as_cpointer = http;
+ result = isc_symtab_define(symtab, name, 1, symvalue,
+ isc_symexists_reject);
+ if (result == ISC_R_EXISTS) {
+ const char *file = NULL;
+ unsigned int line;
+
+ tresult = isc_symtab_lookup(symtab, name, 1, &symvalue);
+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
+
+ line = cfg_obj_line(symvalue.as_cpointer);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+
+ cfg_obj_log(http, logctx, ISC_LOG_ERROR,
+ "http '%s' is duplicated: "
+ "also defined at %s:%u",
+ name, file, line);
+ }
+ }
+
+ /* Check endpoints are valid */
+ tresult = cfg_map_get(http, "endpoints", &eps);
+ if (tresult == ISC_R_SUCCESS) {
+ for (elt = cfg_list_first(eps); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *ep = cfg_listelt_value(elt);
+ const char *path = cfg_obj_asstring(ep);
+ if (!isc_nm_http_path_isvalid(path)) {
+ cfg_obj_log(eps, logctx, ISC_LOG_ERROR,
+ "endpoint '%s' is not a "
+ "valid absolute HTTP path",
+ path);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+bind9_check_httpservers(const cfg_obj_t *config, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result, tresult;
+ const cfg_obj_t *obj = NULL;
+ const cfg_listelt_t *elt = NULL;
+ isc_symtab_t *symtab = NULL;
+
+ result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = cfg_map_get(config, "http", &obj);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto done;
+ }
+
+ for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) {
+ obj = cfg_listelt_value(elt);
+ tresult = bind9_check_httpserver(obj, logctx, symtab);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+done:
+ isc_symtab_destroy(&symtab);
+ return (result);
+}
+#endif /* HAVE_LIBNGHTTP2 */
+
+static isc_result_t
+bind9_check_tls_defintion(const cfg_obj_t *tlsobj, const char *name,
+ isc_log_t *logctx, isc_symtab_t *symtab) {
+ isc_result_t result, tresult;
+ const cfg_obj_t *tls_proto_list = NULL, *tls_key = NULL,
+ *tls_cert = NULL, *tls_ciphers = NULL;
+ uint32_t tls_protos = 0;
+ isc_symvalue_t symvalue;
+
+ if (strcasecmp(name, "ephemeral") == 0 || strcasecmp(name, "none") == 0)
+ {
+ cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR,
+ "tls clause name '%s' is reserved for internal use",
+ name);
+ result = ISC_R_FAILURE;
+ } else {
+ /* Check for duplicates */
+ symvalue.as_cpointer = tlsobj;
+ result = isc_symtab_define(symtab, name, 1, symvalue,
+ isc_symexists_reject);
+ if (result == ISC_R_EXISTS) {
+ const char *file = NULL;
+ unsigned int line;
+
+ tresult = isc_symtab_lookup(symtab, name, 1, &symvalue);
+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
+
+ line = cfg_obj_line(symvalue.as_cpointer);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+
+ cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR,
+ "tls clause '%s' is duplicated: "
+ "also defined at %s:%u",
+ name, file, line);
+ }
+ }
+
+ (void)cfg_map_get(tlsobj, "key-file", &tls_key);
+ (void)cfg_map_get(tlsobj, "cert-file", &tls_cert);
+ if ((tls_key == NULL && tls_cert != NULL) ||
+ (tls_cert == NULL && tls_key != NULL))
+ {
+ cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR,
+ "tls '%s': 'cert-file' and 'key-file' must "
+ "both be specified, or both omitted",
+ name);
+ result = ISC_R_FAILURE;
+ }
+
+ /* Check protocols are valid */
+ tresult = cfg_map_get(tlsobj, "protocols", &tls_proto_list);
+ if (tresult == ISC_R_SUCCESS) {
+ const cfg_listelt_t *proto = NULL;
+ INSIST(tls_proto_list != NULL);
+ for (proto = cfg_list_first(tls_proto_list); proto != 0;
+ proto = cfg_list_next(proto))
+ {
+ const cfg_obj_t *tls_proto_obj =
+ cfg_listelt_value(proto);
+ const char *tls_sver = cfg_obj_asstring(tls_proto_obj);
+ const isc_tls_protocol_version_t ver =
+ isc_tls_protocol_name_to_version(tls_sver);
+
+ if (ver == ISC_TLS_PROTO_VER_UNDEFINED) {
+ cfg_obj_log(tls_proto_obj, logctx,
+ ISC_LOG_ERROR,
+ "'%s' is not a valid "
+ "TLS protocol version",
+ tls_sver);
+ result = ISC_R_FAILURE;
+ continue;
+ } else if (!isc_tls_protocol_supported(ver)) {
+ cfg_obj_log(tls_proto_obj, logctx,
+ ISC_LOG_ERROR,
+ "'%s' is not "
+ "supported by the "
+ "cryptographic library version in "
+ "use (%s)",
+ tls_sver, OPENSSL_VERSION_TEXT);
+ result = ISC_R_FAILURE;
+ }
+
+ if ((tls_protos & ver) != 0) {
+ cfg_obj_log(tls_proto_obj, logctx,
+ ISC_LOG_WARNING,
+ "'%s' is specified more than once "
+ "in '%s'",
+ tls_sver, name);
+ result = ISC_R_FAILURE;
+ }
+
+ tls_protos |= ver;
+ }
+
+ if (tls_protos == 0) {
+ cfg_obj_log(tlsobj, logctx, ISC_LOG_ERROR,
+ "tls '%s' does not contain any valid "
+ "TLS protocol versions definitions",
+ name);
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ /* Check cipher list string is valid */
+ tresult = cfg_map_get(tlsobj, "ciphers", &tls_ciphers);
+ if (tresult == ISC_R_SUCCESS) {
+ const char *ciphers = cfg_obj_asstring(tls_ciphers);
+ if (!isc_tls_cipherlist_valid(ciphers)) {
+ cfg_obj_log(tls_ciphers, logctx, ISC_LOG_ERROR,
+ "'ciphers' in the 'tls' clause '%s' is "
+ "not a "
+ "valid cipher list string",
+ name);
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ return (result);
+}
+
+static isc_result_t
+bind9_check_tls_definitions(const cfg_obj_t *config, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result, tresult;
+ const cfg_obj_t *obj = NULL;
+ const cfg_listelt_t *elt = NULL;
+ isc_symtab_t *symtab = NULL;
+
+ result = cfg_map_get(config, "tls", &obj);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ return (result);
+ }
+
+ result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) {
+ const char *name;
+ obj = cfg_listelt_value(elt);
+ name = cfg_obj_asstring(cfg_map_getname(obj));
+ tresult = bind9_check_tls_defintion(obj, name, logctx, symtab);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ isc_symtab_destroy(&symtab);
+
+ return (result);
+}
+
+static isc_result_t
+get_remotes(const cfg_obj_t *cctx, const char *list, const char *name,
+ const cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_obj_t *obj = NULL;
+ const cfg_listelt_t *elt = NULL;
+
+ result = cfg_map_get(cctx, list, &obj);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ elt = cfg_list_first(obj);
+ while (elt != NULL) {
+ const char *listname;
+
+ obj = cfg_listelt_value(elt);
+ listname = cfg_obj_asstring(cfg_tuple_get(obj, "name"));
+
+ if (strcasecmp(listname, name) == 0) {
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+ elt = cfg_list_next(elt);
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+get_remoteservers_def(const char *list, const char *name, const cfg_obj_t *cctx,
+ const cfg_obj_t **ret) {
+ isc_result_t result = ISC_R_NOTFOUND;
+
+ if (strcmp(list, "primaries") == 0) {
+ result = get_remotes(cctx, "primaries", name, ret);
+ if (result != ISC_R_SUCCESS) {
+ result = get_remotes(cctx, "masters", name, ret);
+ }
+ } else if (strcmp(list, "parental-agents") == 0) {
+ result = get_remotes(cctx, "parental-agents", name, ret);
+ }
+ return (result);
+}
+
+static isc_result_t
+validate_remotes(const char *list, const cfg_obj_t *obj,
+ const cfg_obj_t *config, uint32_t *countp, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ uint32_t count = 0;
+ isc_symtab_t *symtab = NULL;
+ isc_symvalue_t symvalue;
+ const cfg_listelt_t *element;
+ const cfg_listelt_t **stack = NULL;
+ uint32_t stackcount = 0, pushed = 0;
+ const cfg_obj_t *listobj;
+
+ REQUIRE(countp != NULL);
+ result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ *countp = count;
+ return (result);
+ }
+
+newlist:
+ listobj = cfg_tuple_get(obj, "addresses");
+ element = cfg_list_first(listobj);
+resume:
+ for (; element != NULL; element = cfg_list_next(element)) {
+ const char *listname;
+ const cfg_obj_t *addr;
+ const cfg_obj_t *key;
+ const cfg_obj_t *tls;
+
+ addr = cfg_tuple_get(cfg_listelt_value(element),
+ "remoteselement");
+ key = cfg_tuple_get(cfg_listelt_value(element), "key");
+ tls = cfg_tuple_get(cfg_listelt_value(element), "tls");
+
+ if (cfg_obj_issockaddr(addr)) {
+ count++;
+ if (cfg_obj_isstring(key)) {
+ const char *str = cfg_obj_asstring(key);
+ dns_fixedname_t fname;
+ dns_name_t *nm = dns_fixedname_initname(&fname);
+ tresult = dns_name_fromstring(nm, str, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "'%s' is not a valid name",
+ str);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+ if (cfg_obj_isstring(tls)) {
+ const char *str = cfg_obj_asstring(tls);
+ dns_fixedname_t fname;
+ dns_name_t *nm = dns_fixedname_initname(&fname);
+ tresult = dns_name_fromstring(nm, str, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(tls, logctx, ISC_LOG_ERROR,
+ "'%s' is not a valid name",
+ str);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ if (strcasecmp(str, "ephemeral") != 0) {
+ const cfg_obj_t *tlsmap = NULL;
+
+ tlsmap = find_maplist(config, "tls",
+ str);
+ if (tlsmap == NULL) {
+ cfg_obj_log(
+ tls, logctx,
+ ISC_LOG_ERROR,
+ "tls '%s' is not "
+ "defined",
+ cfg_obj_asstring(tls));
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ continue;
+ }
+ if (!cfg_obj_isvoid(key)) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "unexpected token '%s'",
+ cfg_obj_asstring(key));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ if (!cfg_obj_isvoid(tls)) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "unexpected token '%s'",
+ cfg_obj_asstring(tls));
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ listname = cfg_obj_asstring(addr);
+ symvalue.as_cpointer = addr;
+ tresult = isc_symtab_define(symtab, listname, 1, symvalue,
+ isc_symexists_reject);
+ if (tresult == ISC_R_EXISTS) {
+ continue;
+ }
+ tresult = get_remoteservers_def(list, listname, config, &obj);
+ if (tresult != ISC_R_SUCCESS) {
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ cfg_obj_log(addr, logctx, ISC_LOG_ERROR,
+ "unable to find %s list '%s'", list,
+ listname);
+ continue;
+ }
+ /* Grow stack? */
+ if (stackcount == pushed) {
+ void *newstack;
+ uint32_t newlen = stackcount + 16;
+ size_t newsize, oldsize;
+
+ newsize = newlen * sizeof(*stack);
+ oldsize = stackcount * sizeof(*stack);
+ newstack = isc_mem_get(mctx, newsize);
+ if (stackcount != 0) {
+ void *ptr;
+
+ DE_CONST(stack, ptr);
+ memmove(newstack, stack, oldsize);
+ isc_mem_put(mctx, ptr, oldsize);
+ }
+ stack = newstack;
+ stackcount = newlen;
+ }
+ stack[pushed++] = cfg_list_next(element);
+ goto newlist;
+ }
+ if (pushed != 0) {
+ element = stack[--pushed];
+ goto resume;
+ }
+ if (stack != NULL) {
+ void *ptr;
+
+ DE_CONST(stack, ptr);
+ isc_mem_put(mctx, ptr, stackcount * sizeof(*stack));
+ }
+ isc_symtab_destroy(&symtab);
+ *countp = count;
+ return (result);
+}
+
+static isc_result_t
+check_update_policy(const cfg_obj_t *policy, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *element;
+ const cfg_listelt_t *element2;
+ dns_fixedname_t fixed_id, fixed_name;
+ dns_name_t *id, *name;
+ const char *str;
+ isc_textregion_t r;
+ dns_rdatatype_t type;
+
+ /* Check for "update-policy local;" */
+ if (cfg_obj_isstring(policy) &&
+ strcmp("local", cfg_obj_asstring(policy)) == 0)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ /* Now check the grant policy */
+ for (element = cfg_list_first(policy); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *stmt = cfg_listelt_value(element);
+ const cfg_obj_t *identity = cfg_tuple_get(stmt, "identity");
+ const cfg_obj_t *matchtype = cfg_tuple_get(stmt, "matchtype");
+ const cfg_obj_t *dname = cfg_tuple_get(stmt, "name");
+ const cfg_obj_t *typelist = cfg_tuple_get(stmt, "types");
+ dns_ssumatchtype_t mtype;
+
+ id = dns_fixedname_initname(&fixed_id);
+ name = dns_fixedname_initname(&fixed_name);
+
+ tresult = dns_ssu_mtypefromstring(cfg_obj_asstring(matchtype),
+ &mtype);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "has a bad match-type");
+ }
+
+ str = cfg_obj_asstring(identity);
+ tresult = dns_name_fromstring(id, str, 1, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "'%s' is not a valid name", str);
+ result = tresult;
+ }
+
+ /*
+ * There is no name field for subzone and dname is void
+ */
+ if (mtype == dns_ssumatchtype_subdomain &&
+ cfg_obj_isvoid(dname))
+ {
+ str = "."; /* Use "." as a replacement. */
+ } else {
+ str = cfg_obj_asstring(dname);
+ }
+ if (tresult == ISC_R_SUCCESS) {
+ tresult = dns_name_fromstring(name, str, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(dname, logctx, ISC_LOG_ERROR,
+ "'%s' is not a valid name", str);
+ result = tresult;
+ }
+ }
+
+ if (tresult == ISC_R_SUCCESS &&
+ mtype == dns_ssumatchtype_wildcard &&
+ !dns_name_iswildcard(name))
+ {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "'%s' is not a wildcard", str);
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * For some match types, the name should be a placeholder
+ * value, either "." or the same as identity.
+ */
+ switch (mtype) {
+ case dns_ssumatchtype_self:
+ case dns_ssumatchtype_selfsub:
+ case dns_ssumatchtype_selfwild:
+ if (tresult == ISC_R_SUCCESS &&
+ (!dns_name_equal(id, name) &&
+ !dns_name_equal(dns_rootname, name)))
+ {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "identity and name fields are not "
+ "the same");
+ result = ISC_R_FAILURE;
+ }
+ break;
+ case dns_ssumatchtype_selfkrb5:
+ case dns_ssumatchtype_selfms:
+ case dns_ssumatchtype_selfsubkrb5:
+ case dns_ssumatchtype_selfsubms:
+ case dns_ssumatchtype_tcpself:
+ case dns_ssumatchtype_6to4self:
+ if (tresult == ISC_R_SUCCESS &&
+ !dns_name_equal(dns_rootname, name))
+ {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "name field not set to "
+ "placeholder value '.'");
+ result = ISC_R_FAILURE;
+ }
+ break;
+ case dns_ssumatchtype_name:
+ case dns_ssumatchtype_subdomain: /* also zonesub */
+ case dns_ssumatchtype_subdomainms:
+ case dns_ssumatchtype_subdomainselfmsrhs:
+ case dns_ssumatchtype_subdomainkrb5:
+ case dns_ssumatchtype_subdomainselfkrb5rhs:
+ case dns_ssumatchtype_wildcard:
+ case dns_ssumatchtype_external:
+ case dns_ssumatchtype_local:
+ if (tresult == ISC_R_SUCCESS) {
+ DE_CONST(str, r.base);
+ r.length = strlen(str);
+ tresult = dns_rdatatype_fromtext(&type, &r);
+ }
+ if (tresult == ISC_R_SUCCESS) {
+ cfg_obj_log(identity, logctx, ISC_LOG_ERROR,
+ "missing name field type '%s' "
+ "found",
+ str);
+ result = ISC_R_FAILURE;
+ break;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ for (element2 = cfg_list_first(typelist); element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ const cfg_obj_t *typeobj;
+ const char *bracket;
+
+ typeobj = cfg_listelt_value(element2);
+ DE_CONST(cfg_obj_asstring(typeobj), r.base);
+
+ bracket = strchr(r.base, '(' /*)*/);
+ if (bracket != NULL) {
+ char *end = NULL;
+ unsigned long max;
+
+ r.length = bracket - r.base;
+ max = strtoul(bracket + 1, &end, 10);
+ if (max > 0xffff || end[0] != /*(*/ ')' ||
+ end[1] != 0)
+ {
+ cfg_obj_log(typeobj, logctx,
+ ISC_LOG_ERROR,
+ "'%s' is not a valid count",
+ bracket);
+ result = DNS_R_SYNTAX;
+ }
+ } else {
+ r.length = strlen(r.base);
+ }
+
+ tresult = dns_rdatatype_fromtext(&type, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(typeobj, logctx, ISC_LOG_ERROR,
+ "'%.*s' is not a valid type",
+ (int)r.length, r.base);
+ result = tresult;
+ }
+ }
+ }
+ return (result);
+}
+
+typedef struct {
+ const char *name;
+ unsigned int allowed;
+} optionstable;
+
+static isc_result_t
+check_nonzero(const cfg_obj_t *options, isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_obj_t *obj = NULL;
+ unsigned int i;
+
+ static const char *nonzero[] = { "max-retry-time", "min-retry-time",
+ "max-refresh-time",
+ "min-refresh-time" };
+ /*
+ * Check if value is zero.
+ */
+ for (i = 0; i < sizeof(nonzero) / sizeof(nonzero[0]); i++) {
+ obj = NULL;
+ if (cfg_map_get(options, nonzero[i], &obj) == ISC_R_SUCCESS &&
+ cfg_obj_asuint32(obj) == 0)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'%s' must not be zero", nonzero[i]);
+ result = ISC_R_FAILURE;
+ }
+ }
+ return (result);
+}
+
+/*%
+ * Check whether NOTIFY configuration at the zone level is acceptable for a
+ * mirror zone. Return true if it is; return false otherwise.
+ */
+static bool
+check_mirror_zone_notify(const cfg_obj_t *zoptions, const char *znamestr,
+ isc_log_t *logctx) {
+ bool notify_configuration_ok = true;
+ const cfg_obj_t *obj = NULL;
+
+ (void)cfg_map_get(zoptions, "notify", &obj);
+ if (obj == NULL) {
+ /*
+ * "notify" not set at zone level. This is fine.
+ */
+ return (true);
+ }
+
+ if (cfg_obj_isboolean(obj)) {
+ if (cfg_obj_asboolean(obj)) {
+ /*
+ * "notify yes;" set at zone level. This is an error.
+ */
+ notify_configuration_ok = false;
+ }
+ } else {
+ const char *notifystr = cfg_obj_asstring(obj);
+ if (strcasecmp(notifystr, "explicit") != 0) {
+ /*
+ * Something else than "notify explicit;" set at zone
+ * level. This is an error.
+ */
+ notify_configuration_ok = false;
+ }
+ }
+
+ if (!notify_configuration_ok) {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': mirror zones can only be used with "
+ "'notify no;' or 'notify explicit;'",
+ znamestr);
+ }
+
+ return (notify_configuration_ok);
+}
+
+/*%
+ * Try to determine whether recursion is available in a view without resorting
+ * to extraordinary measures: just check the "recursion" and "allow-recursion"
+ * settings. The point is to prevent accidental mirror zone misuse rather than
+ * to enforce some sort of policy. Recursion is assumed to be allowed by
+ * default if it is not explicitly disabled.
+ */
+static bool
+check_recursion(const cfg_obj_t *config, const cfg_obj_t *voptions,
+ const cfg_obj_t *goptions, isc_log_t *logctx,
+ cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
+ dns_acl_t *acl = NULL;
+ const cfg_obj_t *obj;
+ isc_result_t result;
+ bool retval = true;
+
+ /*
+ * Check the "recursion" option first.
+ */
+ obj = NULL;
+ result = ISC_R_NOTFOUND;
+ if (voptions != NULL) {
+ result = cfg_map_get(voptions, "recursion", &obj);
+ }
+ if (result != ISC_R_SUCCESS && goptions != NULL) {
+ result = cfg_map_get(goptions, "recursion", &obj);
+ }
+ if (result == ISC_R_SUCCESS && !cfg_obj_asboolean(obj)) {
+ retval = false;
+ goto cleanup;
+ }
+
+ /*
+ * If recursion is not disabled by the "recursion" option, check
+ * whether it is disabled by the "allow-recursion" ACL.
+ */
+ obj = NULL;
+ result = ISC_R_NOTFOUND;
+ if (voptions != NULL) {
+ result = cfg_map_get(voptions, "allow-recursion", &obj);
+ }
+ if (result != ISC_R_SUCCESS && goptions != NULL) {
+ result = cfg_map_get(goptions, "allow-recursion", &obj);
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = cfg_acl_fromconfig(obj, config, logctx, actx, mctx, 0,
+ &acl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ retval = !dns_acl_isnone(acl);
+ }
+
+cleanup:
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+
+ return (retval);
+}
+
+static isc_result_t
+check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
+ const cfg_obj_t *config, isc_symtab_t *symtab,
+ isc_symtab_t *files, isc_symtab_t *keydirs, isc_symtab_t *inview,
+ const char *viewname, dns_rdataclass_t defclass,
+ bool nodeprecate, cfg_aclconfctx_t *actx, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ const char *znamestr;
+ const char *typestr = NULL;
+ const char *target = NULL;
+ unsigned int ztype;
+ const cfg_obj_t *zoptions, *goptions = NULL;
+ const cfg_obj_t *obj = NULL, *kasp = NULL;
+ const cfg_obj_t *inviewobj = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ unsigned int i;
+ dns_rdataclass_t zclass;
+ dns_fixedname_t fixedname;
+ dns_name_t *zname = NULL; /* NULL if parsing of zone name fails. */
+ isc_buffer_t b;
+ bool root = false;
+ bool rfc1918 = false;
+ bool ula = false;
+ const cfg_listelt_t *element;
+ bool dlz;
+ bool ddns = false;
+ bool has_dnssecpolicy = false;
+ const void *clauses = NULL;
+ const char *option = NULL;
+ const char *kaspname = NULL;
+ const char *dir = NULL;
+ static const char *acls[] = {
+ "allow-notify",
+ "allow-transfer",
+ "allow-update",
+ "allow-update-forwarding",
+ };
+ static optionstable dialups[] = {
+ { "notify", CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "notify-passive", CFG_ZONE_SECONDARY },
+ { "passive", CFG_ZONE_SECONDARY | CFG_ZONE_STUB },
+ { "refresh", CFG_ZONE_SECONDARY | CFG_ZONE_STUB },
+ };
+ static const char *sources[] = {
+ "transfer-source", "transfer-source-v6", "notify-source",
+ "notify-source-v6", "parental-source", "parental-source-v6",
+ };
+
+ znamestr = cfg_obj_asstring(cfg_tuple_get(zconfig, "name"));
+
+ zoptions = cfg_tuple_get(zconfig, "options");
+
+ if (config != NULL) {
+ cfg_map_get(config, "options", &goptions);
+ }
+
+ inviewobj = NULL;
+ (void)cfg_map_get(zoptions, "in-view", &inviewobj);
+ if (inviewobj != NULL) {
+ target = cfg_obj_asstring(inviewobj);
+ ztype = CFG_ZONE_INVIEW;
+ } else {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "type", &obj);
+ if (obj == NULL) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': type not present", znamestr);
+ return (ISC_R_FAILURE);
+ }
+
+ typestr = cfg_obj_asstring(obj);
+ if (strcasecmp(typestr, "master") == 0 ||
+ strcasecmp(typestr, "primary") == 0)
+ {
+ ztype = CFG_ZONE_PRIMARY;
+ } else if (strcasecmp(typestr, "slave") == 0 ||
+ strcasecmp(typestr, "secondary") == 0)
+ {
+ ztype = CFG_ZONE_SECONDARY;
+ } else if (strcasecmp(typestr, "mirror") == 0) {
+ ztype = CFG_ZONE_MIRROR;
+ } else if (strcasecmp(typestr, "stub") == 0) {
+ ztype = CFG_ZONE_STUB;
+ } else if (strcasecmp(typestr, "static-stub") == 0) {
+ ztype = CFG_ZONE_STATICSTUB;
+ } else if (strcasecmp(typestr, "forward") == 0) {
+ ztype = CFG_ZONE_FORWARD;
+ } else if (strcasecmp(typestr, "hint") == 0) {
+ ztype = CFG_ZONE_HINT;
+ } else if (strcasecmp(typestr, "delegation-only") == 0) {
+ ztype = CFG_ZONE_DELEGATION;
+ if (!nodeprecate) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'type delegation-only' is "
+ "deprecated");
+ }
+ } else if (strcasecmp(typestr, "redirect") == 0) {
+ ztype = CFG_ZONE_REDIRECT;
+ } else {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "zone '%s': invalid type %s", znamestr,
+ typestr);
+ return (ISC_R_FAILURE);
+ }
+
+ if (ztype == CFG_ZONE_REDIRECT && strcmp(znamestr, ".") != 0) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "redirect zones must be called \".\"");
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ obj = cfg_tuple_get(zconfig, "class");
+ if (cfg_obj_isstring(obj)) {
+ isc_textregion_t r;
+
+ DE_CONST(cfg_obj_asstring(obj), r.base);
+ r.length = strlen(r.base);
+ result = dns_rdataclass_fromtext(&zclass, &r);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "zone '%s': invalid class %s", znamestr,
+ r.base);
+ return (ISC_R_FAILURE);
+ }
+ if (zclass != defclass) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "zone '%s': class '%s' does not "
+ "match view/default class",
+ znamestr, r.base);
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ zclass = defclass;
+ }
+
+ /*
+ * Look for an already existing zone.
+ * We need to make this canonical as isc_symtab_define()
+ * deals with strings.
+ */
+ dns_fixedname_init(&fixedname);
+ isc_buffer_constinit(&b, znamestr, strlen(znamestr));
+ isc_buffer_add(&b, strlen(znamestr));
+ tresult = dns_name_fromtext(dns_fixedname_name(&fixedname), &b,
+ dns_rootname, DNS_NAME_DOWNCASE, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': is not a valid name", znamestr);
+ result = ISC_R_FAILURE;
+ } else {
+ char namebuf[DNS_NAME_FORMATSIZE + 128];
+ char *tmp = namebuf;
+ size_t len = sizeof(namebuf);
+
+ zname = dns_fixedname_name(&fixedname);
+ dns_name_format(zname, namebuf, sizeof(namebuf));
+ tresult = nameexist(zconfig, namebuf,
+ ztype == CFG_ZONE_HINT ? 1
+ : ztype == CFG_ZONE_REDIRECT ? 2
+ : 3,
+ symtab,
+ "zone '%s': already exists "
+ "previous definition: %s:%u",
+ logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ if (dns_name_equal(zname, dns_rootname)) {
+ root = true;
+ } else if (dns_name_isrfc1918(zname)) {
+ rfc1918 = true;
+ } else if (dns_name_isula(zname)) {
+ ula = true;
+ }
+ len -= strlen(tmp);
+ tmp += strlen(tmp);
+ (void)snprintf(tmp, len, "%u/%s", zclass,
+ (ztype == CFG_ZONE_INVIEW) ? target
+ : (viewname != NULL) ? viewname
+ : "_default");
+ switch (ztype) {
+ case CFG_ZONE_INVIEW:
+ tresult = isc_symtab_lookup(inview, namebuf, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(inviewobj, logctx, ISC_LOG_ERROR,
+ "'in-view' zone '%s' "
+ "does not exist in view '%s', "
+ "or view '%s' is not yet defined",
+ znamestr, target, target);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ break;
+
+ case CFG_ZONE_FORWARD:
+ case CFG_ZONE_REDIRECT:
+ case CFG_ZONE_DELEGATION:
+ break;
+
+ case CFG_ZONE_PRIMARY:
+ case CFG_ZONE_SECONDARY:
+ case CFG_ZONE_MIRROR:
+ case CFG_ZONE_HINT:
+ case CFG_ZONE_STUB:
+ case CFG_ZONE_STATICSTUB:
+ tmp = isc_mem_strdup(mctx, namebuf);
+ {
+ isc_symvalue_t symvalue;
+ symvalue.as_cpointer = NULL;
+ tresult = isc_symtab_define(
+ inview, tmp, 1, symvalue,
+ isc_symexists_replace);
+ if (tresult == ISC_R_NOMEMORY) {
+ isc_mem_free(mctx, tmp);
+ }
+ if (result == ISC_R_SUCCESS &&
+ tresult != ISC_R_SUCCESS)
+ {
+ result = tresult;
+ }
+ }
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ if (ztype == CFG_ZONE_INVIEW) {
+ const cfg_obj_t *fwd = NULL;
+ unsigned int maxopts = 1;
+
+ (void)cfg_map_get(zoptions, "forward", &fwd);
+ if (fwd != NULL) {
+ maxopts++;
+ }
+ fwd = NULL;
+ (void)cfg_map_get(zoptions, "forwarders", &fwd);
+ if (fwd != NULL) {
+ maxopts++;
+ }
+ if (cfg_map_count(zoptions) > maxopts) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': 'in-view' used "
+ "with incompatible zone options",
+ znamestr);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ return (result);
+ }
+
+ /*
+ * Check if value is zero.
+ */
+ if (check_nonzero(zoptions, logctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check if a dnssec-policy is set.
+ */
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "dnssec-policy", &obj);
+ if (obj == NULL && voptions != NULL) {
+ (void)cfg_map_get(voptions, "dnssec-policy", &obj);
+ }
+ if (obj == NULL && goptions != NULL) {
+ (void)cfg_map_get(goptions, "dnssec-policy", &obj);
+ }
+ if (obj != NULL) {
+ const cfg_obj_t *kasps = NULL;
+
+ kaspname = cfg_obj_asstring(obj);
+ if (strcmp(kaspname, "default") == 0) {
+ has_dnssecpolicy = true;
+ } else if (strcmp(kaspname, "insecure") == 0) {
+ has_dnssecpolicy = true;
+ } else if (strcmp(kaspname, "none") == 0) {
+ has_dnssecpolicy = false;
+ } else {
+ (void)cfg_map_get(config, "dnssec-policy", &kasps);
+ for (element = cfg_list_first(kasps); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *kobj = cfg_tuple_get(
+ cfg_listelt_value(element), "name");
+ if (strcmp(kaspname, cfg_obj_asstring(kobj)) ==
+ 0)
+ {
+ has_dnssecpolicy = true;
+ }
+ }
+
+ if (!has_dnssecpolicy) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': option "
+ "'dnssec-policy %s' has no "
+ "matching dnssec-policy config",
+ znamestr, kaspname);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ if (has_dnssecpolicy) {
+ kasp = obj;
+ }
+ }
+
+ /*
+ * Warn about zones with both dnssec-policy and max-zone-ttl
+ */
+ if (has_dnssecpolicy) {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "max-zone-ttl", &obj);
+ if (obj == NULL && voptions != NULL) {
+ (void)cfg_map_get(voptions, "max-zone-ttl", &obj);
+ }
+ if (obj == NULL && goptions != NULL) {
+ (void)cfg_map_get(goptions, "max-zone-ttl", &obj);
+ }
+ if (obj != NULL) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "zone '%s': option 'max-zone-ttl' "
+ "is ignored when used together with "
+ "'dnssec-policy'",
+ znamestr);
+ }
+ }
+
+ /*
+ * Check validity of the zone options.
+ */
+ option = cfg_map_firstclause(&cfg_type_zoneopts, &clauses, &i);
+ while (option != NULL) {
+ obj = NULL;
+ if (cfg_map_get(zoptions, option, &obj) == ISC_R_SUCCESS &&
+ obj != NULL && !cfg_clause_validforzone(option, ztype))
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "option '%s' is not allowed "
+ "in '%s' zone '%s'",
+ option, typestr, znamestr);
+ result = ISC_R_FAILURE;
+ }
+ option = cfg_map_nextclause(&cfg_type_zoneopts, &clauses, &i);
+ }
+
+ /*
+ * Check that ACLs expand correctly.
+ */
+ for (i = 0; i < ARRAY_SIZE(acls); i++) {
+ tresult = checkacl(acls[i], actx, zconfig, voptions, config,
+ logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ /*
+ * Only a limited subset of all possible "notify" settings can be used
+ * at the zone level for mirror zones.
+ */
+ if (ztype == CFG_ZONE_MIRROR &&
+ !check_mirror_zone_notify(zoptions, znamestr, logctx))
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Primary, secondary, and mirror zones may have an "also-notify"
+ * field, but shouldn't if notify is disabled.
+ */
+ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY ||
+ ztype == CFG_ZONE_MIRROR)
+ {
+ bool donotify = true;
+
+ obj = NULL;
+ tresult = cfg_map_get(zoptions, "notify", &obj);
+ if (tresult != ISC_R_SUCCESS && voptions != NULL) {
+ tresult = cfg_map_get(voptions, "notify", &obj);
+ }
+ if (tresult != ISC_R_SUCCESS && goptions != NULL) {
+ tresult = cfg_map_get(goptions, "notify", &obj);
+ }
+ if (tresult == ISC_R_SUCCESS) {
+ if (cfg_obj_isboolean(obj)) {
+ donotify = cfg_obj_asboolean(obj);
+ } else {
+ const char *str = cfg_obj_asstring(obj);
+ if (ztype != CFG_ZONE_PRIMARY &&
+ (strcasecmp(str, "master-only") == 0 ||
+ strcasecmp(str, "primary-only") == 0))
+ {
+ donotify = false;
+ }
+ }
+ }
+
+ obj = NULL;
+ tresult = cfg_map_get(zoptions, "also-notify", &obj);
+ if (tresult == ISC_R_SUCCESS && !donotify) {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_WARNING,
+ "zone '%s': 'also-notify' set but "
+ "'notify' is disabled",
+ znamestr);
+ }
+ if (tresult != ISC_R_SUCCESS && voptions != NULL) {
+ tresult = cfg_map_get(voptions, "also-notify", &obj);
+ }
+ if (tresult != ISC_R_SUCCESS && goptions != NULL) {
+ tresult = cfg_map_get(goptions, "also-notify", &obj);
+ }
+ if (tresult == ISC_R_SUCCESS && donotify) {
+ uint32_t count;
+ tresult = validate_remotes("primaries", obj, config,
+ &count, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS)
+ {
+ result = tresult;
+ }
+ }
+ }
+
+ /*
+ * Secondary, mirror, and stub zones must have a "primaries" field,
+ * with one exception: when mirroring the root zone, a default,
+ * built-in primary server list is used in the absence of one
+ * explicitly specified.
+ */
+ if (ztype == CFG_ZONE_SECONDARY || ztype == CFG_ZONE_STUB ||
+ (ztype == CFG_ZONE_MIRROR && zname != NULL &&
+ !dns_name_equal(zname, dns_rootname)))
+ {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "primaries", &obj);
+ if (obj == NULL) {
+ /* If "primaries" was unset, check for "masters" */
+ (void)cfg_map_get(zoptions, "masters", &obj);
+ } else {
+ const cfg_obj_t *obj2 = NULL;
+
+ /* ...bug if it was set, "masters" must not be. */
+ (void)cfg_map_get(zoptions, "masters", &obj2);
+ if (obj2 != NULL) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'primaries' and 'masters' cannot "
+ "both be used in the same zone");
+ result = ISC_R_FAILURE;
+ }
+ }
+ if (obj == NULL) {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': missing 'primaries' entry",
+ znamestr);
+ result = ISC_R_FAILURE;
+ } else {
+ uint32_t count;
+ tresult = validate_remotes("primaries", obj, config,
+ &count, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS)
+ {
+ result = tresult;
+ }
+ if (tresult == ISC_R_SUCCESS && count == 0) {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': "
+ "empty 'primaries' entry",
+ znamestr);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Warn if *-source and *-source-v6 options specify a port,
+ * and fail if they specify the default listener port.
+ */
+ for (i = 0; i < ARRAY_SIZE(sources); i++) {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, sources[i], &obj);
+ if (obj == NULL && goptions != NULL) {
+ (void)cfg_map_get(goptions, sources[i], &obj);
+ }
+ if (obj != NULL) {
+ in_port_t port =
+ isc_sockaddr_getport(cfg_obj_assockaddr(obj));
+ if (port == dnsport) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'%s' cannot specify the "
+ "DNS listener port (%d)",
+ sources[i], port);
+ result = ISC_R_FAILURE;
+ } else if (port != 0) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'%s': specifying a port is "
+ "not recommended",
+ sources[i]);
+ }
+ }
+ }
+
+ /*
+ * Primary and secondary zones that have a "parental-agents" field,
+ * must have a corresponding "parental-agents" clause.
+ */
+ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "parental-agents", &obj);
+ if (obj != NULL) {
+ uint32_t count;
+ tresult = validate_remotes("parental-agents", obj,
+ config, &count, logctx,
+ mctx);
+ if (tresult != ISC_R_SUCCESS && result == ISC_R_SUCCESS)
+ {
+ result = tresult;
+ }
+ if (tresult == ISC_R_SUCCESS && count == 0) {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': "
+ "empty 'parental-agents' entry",
+ znamestr);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Configuring a mirror zone and disabling recursion at the same time
+ * contradicts the purpose of the former.
+ */
+ if (ztype == CFG_ZONE_MIRROR &&
+ !check_recursion(config, voptions, goptions, logctx, actx, mctx))
+ {
+ cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
+ "zone '%s': mirror zones cannot be used if "
+ "recursion is disabled",
+ znamestr);
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Primary zones can't have both "allow-update" and "update-policy".
+ */
+ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY) {
+ bool signing = false;
+ isc_result_t res1, res2, res3;
+ const cfg_obj_t *au = NULL;
+ const char *arg;
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "allow-update", &au);
+ obj = NULL;
+ res2 = cfg_map_get(zoptions, "update-policy", &obj);
+ if (res1 == ISC_R_SUCCESS && res2 == ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "zone '%s': 'allow-update' is ignored "
+ "when 'update-policy' is present",
+ znamestr);
+ result = ISC_R_FAILURE;
+ } else if (res2 == ISC_R_SUCCESS) {
+ res3 = check_update_policy(obj, logctx);
+ if (res3 != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ /*
+ * To determine whether auto-dnssec is allowed,
+ * we should also check for allow-update at the
+ * view and options levels.
+ */
+ if (res1 != ISC_R_SUCCESS && voptions != NULL) {
+ res1 = cfg_map_get(voptions, "allow-update", &au);
+ }
+ if (res1 != ISC_R_SUCCESS && goptions != NULL) {
+ res1 = cfg_map_get(goptions, "allow-update", &au);
+ }
+
+ if (res2 == ISC_R_SUCCESS) {
+ ddns = true;
+ } else if (res1 == ISC_R_SUCCESS) {
+ dns_acl_t *acl = NULL;
+ res1 = cfg_acl_fromconfig(au, config, logctx, actx,
+ mctx, 0, &acl);
+ if (res1 != ISC_R_SUCCESS) {
+ cfg_obj_log(au, logctx, ISC_LOG_ERROR,
+ "acl expansion failed: %s",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ } else if (acl != NULL) {
+ if (!dns_acl_isnone(acl)) {
+ ddns = true;
+ }
+ dns_acl_detach(&acl);
+ }
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "inline-signing", &obj);
+ if (res1 == ISC_R_SUCCESS) {
+ signing = cfg_obj_asboolean(obj);
+ }
+
+ if (has_dnssecpolicy) {
+ if (!ddns && !signing) {
+ cfg_obj_log(kasp, logctx, ISC_LOG_ERROR,
+ "'inline-signing yes;' must also "
+ "be configured explicitly for "
+ "zones using dnssec-policy%s. See "
+ "https://kb.isc.org/docs/"
+ "dnssec-policy-requires-dynamic-"
+ "dns-or-inline-signing",
+ (ztype == CFG_ZONE_PRIMARY)
+ ? " without a configured "
+ "'allow-update' or "
+ "'update-policy'"
+ : "");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ arg = "off";
+ res3 = cfg_map_get(zoptions, "auto-dnssec", &obj);
+ if (res3 == ISC_R_SUCCESS) {
+ arg = cfg_obj_asstring(obj);
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "'auto-dnssec' option is deprecated and "
+ "will be removed in BIND 9.19. Please "
+ "migrate to dnssec-policy");
+ }
+ if (strcasecmp(arg, "off") != 0) {
+ if (!ddns && !signing && !has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'auto-dnssec %s;' requires%s "
+ "inline-signing to be configured "
+ "for the zone",
+ arg,
+ (ztype == CFG_ZONE_PRIMARY)
+ ? " dynamic DNS or"
+ : "");
+ result = ISC_R_FAILURE;
+ }
+
+ if (has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'auto-dnssec %s;' cannot be "
+ "configured if dnssec-policy is "
+ "also set",
+ arg);
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "sig-signing-type", &obj);
+ if (res1 == ISC_R_SUCCESS) {
+ uint32_t type = cfg_obj_asuint32(obj);
+ if (type < 0xff00U || type > 0xffffU) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "sig-signing-type: %u out of "
+ "range [%u..%u]",
+ type, 0xff00U, 0xffffU);
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "dnssec-dnskey-kskonly", &obj);
+ if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY &&
+ !signing)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-dnskey-kskonly: requires "
+ "inline-signing when used in secondary "
+ "zone");
+ result = ISC_R_FAILURE;
+ }
+ if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-dnskey-kskonly: cannot be "
+ "configured if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "dnssec-secure-to-insecure", &obj);
+ if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-secure-to-insecure: cannot be "
+ "configured if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "dnssec-loadkeys-interval", &obj);
+ if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY &&
+ !signing)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-loadkeys-interval: requires "
+ "inline-signing when used in secondary "
+ "zone");
+ result = ISC_R_FAILURE;
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "update-check-ksk", &obj);
+ if (res1 == ISC_R_SUCCESS && ztype == CFG_ZONE_SECONDARY &&
+ !signing)
+ {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "update-check-ksk: requires "
+ "inline-signing when used in secondary "
+ "zone");
+ result = ISC_R_FAILURE;
+ }
+ if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "update-check-ksk: cannot be configured "
+ "if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "dnssec-update-mode", &obj);
+ if (res1 == ISC_R_SUCCESS && has_dnssecpolicy) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-update-mode: cannot be configured "
+ "if dnssec-policy is also set");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ /*
+ * Check the excessively complicated "dialup" option.
+ */
+ if (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_SECONDARY ||
+ ztype == CFG_ZONE_STUB)
+ {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "dialup", &obj);
+ if (obj != NULL && cfg_obj_isstring(obj)) {
+ const char *str = cfg_obj_asstring(obj);
+ for (i = 0; i < sizeof(dialups) / sizeof(dialups[0]);
+ i++)
+ {
+ if (strcasecmp(dialups[i].name, str) != 0) {
+ continue;
+ }
+ if ((dialups[i].allowed & ztype) == 0) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dialup type '%s' is not "
+ "allowed in '%s' "
+ "zone '%s'",
+ str, typestr, znamestr);
+ result = ISC_R_FAILURE;
+ }
+ break;
+ }
+ if (i == sizeof(dialups) / sizeof(dialups[0])) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "invalid dialup type '%s' in zone "
+ "'%s'",
+ str, znamestr);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Check that forwarding is reasonable.
+ */
+ obj = NULL;
+ if (root) {
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "forwarders", &obj);
+ }
+ if (obj == NULL && goptions != NULL) {
+ (void)cfg_map_get(goptions, "forwarders", &obj);
+ }
+ }
+ if (check_forward(zoptions, obj, logctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check that a RFC 1918 / ULA reverse zone is not forward first
+ * unless explicitly configured to be so.
+ */
+ if (ztype == CFG_ZONE_FORWARD && (rfc1918 || ula)) {
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "forward", &obj);
+ if (obj == NULL) {
+ /*
+ * Forward mode not explicitly configured.
+ */
+ if (voptions != NULL) {
+ cfg_map_get(voptions, "forward", &obj);
+ }
+ if (obj == NULL && goptions != NULL) {
+ cfg_map_get(goptions, "forward", &obj);
+ }
+ if (obj == NULL ||
+ strcasecmp(cfg_obj_asstring(obj), "first") == 0)
+ {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_WARNING,
+ "inherited 'forward first;' for "
+ "%s zone '%s' - did you want "
+ "'forward only;'?",
+ rfc1918 ? "rfc1918" : "ula",
+ znamestr);
+ }
+ }
+ }
+
+ /*
+ * Check validity of static stub server addresses.
+ */
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "server-addresses", &obj);
+ if (ztype == CFG_ZONE_STATICSTUB && obj != NULL) {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ isc_sockaddr_t sa;
+ isc_netaddr_t na;
+ obj = cfg_listelt_value(element);
+ sa = *cfg_obj_assockaddr(obj);
+
+ isc_netaddr_fromsockaddr(&na, &sa);
+ if (isc_netaddr_getzone(&na) != 0) {
+ result = ISC_R_FAILURE;
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "scoped address is not allowed "
+ "for static stub "
+ "server-addresses");
+ }
+ }
+ }
+
+ /*
+ * Check validity of static stub server names.
+ */
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "server-names", &obj);
+ if (zname != NULL && ztype == CFG_ZONE_STATICSTUB && obj != NULL) {
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const char *snamestr;
+ dns_fixedname_t fixed_sname;
+ isc_buffer_t b2;
+ dns_name_t *sname;
+
+ obj = cfg_listelt_value(element);
+ snamestr = cfg_obj_asstring(obj);
+
+ isc_buffer_constinit(&b2, snamestr, strlen(snamestr));
+ isc_buffer_add(&b2, strlen(snamestr));
+ sname = dns_fixedname_initname(&fixed_sname);
+ tresult = dns_name_fromtext(sname, &b2, dns_rootname, 0,
+ NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "server-name '%s' is not a valid "
+ "name",
+ snamestr);
+ result = ISC_R_FAILURE;
+ } else if (dns_name_issubdomain(sname, zname)) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "server-name '%s' must not be a "
+ "subdomain of zone name '%s'",
+ snamestr, znamestr);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+
+ /*
+ * Warn if key-directory doesn't exist
+ */
+ obj = NULL;
+ (void)cfg_map_get(zoptions, "key-directory", &obj);
+ if (obj == NULL && voptions != NULL) {
+ (void)cfg_map_get(voptions, "key-directory", &obj);
+ }
+ if (obj == NULL && goptions != NULL) {
+ (void)cfg_map_get(goptions, "key-directory", &obj);
+ }
+ if (obj != NULL) {
+ dir = cfg_obj_asstring(obj);
+
+ tresult = isc_file_isdirectory(dir);
+ switch (tresult) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_FILENOTFOUND:
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "key-directory: '%s' does not exist", dir);
+ break;
+ case ISC_R_INVALIDFILE:
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "key-directory: '%s' is not a directory",
+ dir);
+ break;
+ default:
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "key-directory: '%s' %s", dir,
+ isc_result_totext(tresult));
+ result = tresult;
+ }
+ }
+
+ /*
+ * Make sure there is no other zone with the same
+ * key-directory and a different dnssec-policy.
+ */
+ if (zname != NULL) {
+ char keydirbuf[DNS_NAME_FORMATSIZE + 128];
+ char *tmp = keydirbuf;
+ size_t len = sizeof(keydirbuf);
+ dns_name_format(zname, keydirbuf, sizeof(keydirbuf));
+ len -= strlen(tmp);
+ tmp += strlen(tmp);
+ (void)snprintf(tmp, len, "/%s", (dir == NULL) ? "(null)" : dir);
+ tresult = keydirexist(zconfig, (const char *)keydirbuf,
+ kaspname, keydirs, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ /*
+ * Check various options.
+ */
+ tresult = check_options(zoptions, config, logctx, mctx, optlevel_zone);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ /*
+ * If the zone type is rbt/rbt64 then primary/hint zones require file
+ * clauses. If inline-signing is used, then secondary zones require a
+ * file clause as well.
+ */
+ obj = NULL;
+ dlz = false;
+ tresult = cfg_map_get(zoptions, "dlz", &obj);
+ if (tresult == ISC_R_SUCCESS) {
+ dlz = true;
+ }
+
+ obj = NULL;
+ tresult = cfg_map_get(zoptions, "database", &obj);
+ if (dlz && tresult == ISC_R_SUCCESS) {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': cannot specify both 'dlz' "
+ "and 'database'",
+ znamestr);
+ result = ISC_R_FAILURE;
+ } else if (!dlz && (tresult == ISC_R_NOTFOUND ||
+ (tresult == ISC_R_SUCCESS &&
+ (strcmp("rbt", cfg_obj_asstring(obj)) == 0 ||
+ strcmp("rbt64", cfg_obj_asstring(obj)) == 0))))
+ {
+ isc_result_t res1;
+ const cfg_obj_t *fileobj = NULL;
+ tresult = cfg_map_get(zoptions, "file", &fileobj);
+ obj = NULL;
+ res1 = cfg_map_get(zoptions, "inline-signing", &obj);
+ if ((tresult != ISC_R_SUCCESS &&
+ (ztype == CFG_ZONE_PRIMARY || ztype == CFG_ZONE_HINT ||
+ (ztype == CFG_ZONE_SECONDARY && res1 == ISC_R_SUCCESS &&
+ cfg_obj_asboolean(obj)))))
+ {
+ cfg_obj_log(zconfig, logctx, ISC_LOG_ERROR,
+ "zone '%s': missing 'file' entry",
+ znamestr);
+ result = tresult;
+ } else if (tresult == ISC_R_SUCCESS &&
+ (ztype == CFG_ZONE_SECONDARY ||
+ ztype == CFG_ZONE_MIRROR || ddns ||
+ has_dnssecpolicy))
+ {
+ tresult = fileexist(fileobj, files, true, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ } else if (tresult == ISC_R_SUCCESS &&
+ (ztype == CFG_ZONE_PRIMARY ||
+ ztype == CFG_ZONE_HINT))
+ {
+ tresult = fileexist(fileobj, files, false, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ return (result);
+}
+
+typedef struct keyalgorithms {
+ const char *name;
+ uint16_t size;
+} algorithmtable;
+
+isc_result_t
+bind9_check_key(const cfg_obj_t *key, isc_log_t *logctx) {
+ const cfg_obj_t *algobj = NULL;
+ const cfg_obj_t *secretobj = NULL;
+ const char *keyname = cfg_obj_asstring(cfg_map_getname(key));
+ const char *algorithm;
+ int i;
+ size_t len = 0;
+ isc_result_t result;
+ isc_buffer_t buf;
+ unsigned char secretbuf[1024];
+ static const algorithmtable algorithms[] = {
+ { "hmac-md5", 128 },
+ { "hmac-md5.sig-alg.reg.int", 0 },
+ { "hmac-md5.sig-alg.reg.int.", 0 },
+ { "hmac-sha1", 160 },
+ { "hmac-sha224", 224 },
+ { "hmac-sha256", 256 },
+ { "hmac-sha384", 384 },
+ { "hmac-sha512", 512 },
+ { NULL, 0 }
+ };
+
+ (void)cfg_map_get(key, "algorithm", &algobj);
+ (void)cfg_map_get(key, "secret", &secretobj);
+ if (secretobj == NULL || algobj == NULL) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "key '%s' must have both 'secret' and "
+ "'algorithm' defined",
+ keyname);
+ return (ISC_R_FAILURE);
+ }
+
+ isc_buffer_init(&buf, secretbuf, sizeof(secretbuf));
+ result = isc_base64_decodestring(cfg_obj_asstring(secretobj), &buf);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(secretobj, logctx, ISC_LOG_ERROR, "bad secret '%s'",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ algorithm = cfg_obj_asstring(algobj);
+ for (i = 0; algorithms[i].name != NULL; i++) {
+ len = strlen(algorithms[i].name);
+ if (strncasecmp(algorithms[i].name, algorithm, len) == 0 &&
+ (algorithm[len] == '\0' ||
+ (algorithms[i].size != 0 && algorithm[len] == '-')))
+ {
+ break;
+ }
+ }
+ if (algorithms[i].name == NULL) {
+ cfg_obj_log(algobj, logctx, ISC_LOG_ERROR,
+ "unknown algorithm '%s'", algorithm);
+ return (ISC_R_NOTFOUND);
+ }
+ if (algorithm[len] == '-') {
+ uint16_t digestbits;
+ result = isc_parse_uint16(&digestbits, algorithm + len + 1, 10);
+ if (result == ISC_R_SUCCESS || result == ISC_R_RANGE) {
+ if (result == ISC_R_RANGE ||
+ digestbits > algorithms[i].size)
+ {
+ cfg_obj_log(algobj, logctx, ISC_LOG_ERROR,
+ "key '%s' digest-bits too large "
+ "[%u..%u]",
+ keyname, algorithms[i].size / 2,
+ algorithms[i].size);
+ return (ISC_R_RANGE);
+ }
+ if ((digestbits % 8) != 0) {
+ cfg_obj_log(algobj, logctx, ISC_LOG_ERROR,
+ "key '%s' digest-bits not multiple"
+ " of 8",
+ keyname);
+ return (ISC_R_RANGE);
+ }
+ /*
+ * Recommended minima for hmac algorithms.
+ */
+ if ((digestbits < (algorithms[i].size / 2U) ||
+ (digestbits < 80U)))
+ {
+ cfg_obj_log(algobj, logctx, ISC_LOG_WARNING,
+ "key '%s' digest-bits too small "
+ "[<%u]",
+ keyname, algorithms[i].size / 2);
+ }
+ } else {
+ cfg_obj_log(algobj, logctx, ISC_LOG_ERROR,
+ "key '%s': unable to parse digest-bits",
+ keyname);
+ return (result);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable,
+ isc_log_t *logctx) {
+ isc_result_t result;
+ isc_symvalue_t symvalue;
+ unsigned int line;
+ const char *file;
+
+ result = isc_symtab_lookup(symtab, cfg_obj_asstring(obj), 0, &symvalue);
+ if (result == ISC_R_SUCCESS) {
+ if (writeable) {
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "writeable file '%s': already in use: "
+ "%s:%u",
+ cfg_obj_asstring(obj), file, line);
+ return (ISC_R_EXISTS);
+ }
+ result = isc_symtab_lookup(symtab, cfg_obj_asstring(obj), 2,
+ &symvalue);
+ if (result == ISC_R_SUCCESS) {
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "writeable file '%s': already in use: "
+ "%s:%u",
+ cfg_obj_asstring(obj), file, line);
+ return (ISC_R_EXISTS);
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ symvalue.as_cpointer = obj;
+ result = isc_symtab_define(symtab, cfg_obj_asstring(obj),
+ writeable ? 2 : 1, symvalue,
+ isc_symexists_reject);
+ return (result);
+}
+
+static isc_result_t
+keydirexist(const cfg_obj_t *zcfg, const char *keydir, const char *kaspnamestr,
+ isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx) {
+ isc_result_t result;
+ isc_symvalue_t symvalue;
+ char *symkey;
+
+ if (kaspnamestr == NULL || strcmp(kaspnamestr, "none") == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = isc_symtab_lookup(symtab, keydir, 0, &symvalue);
+ if (result == ISC_R_SUCCESS) {
+ const cfg_obj_t *kasp = NULL;
+ const cfg_obj_t *exist = symvalue.as_cpointer;
+ const char *file = cfg_obj_file(exist);
+ unsigned int line = cfg_obj_line(exist);
+
+ /*
+ * Having the same key-directory for the same zone is fine
+ * iff the zone is using the same policy, or has no policy.
+ */
+ (void)cfg_map_get(cfg_tuple_get(exist, "options"),
+ "dnssec-policy", &kasp);
+ if (kasp == NULL ||
+ strcmp(cfg_obj_asstring(kasp), "none") == 0 ||
+ strcmp(cfg_obj_asstring(kasp), kaspnamestr) == 0)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_obj_log(zcfg, logctx, ISC_LOG_ERROR,
+ "key-directory '%s' already in use by zone %s with "
+ "policy %s: %s:%u",
+ keydir,
+ cfg_obj_asstring(cfg_tuple_get(exist, "name")),
+ cfg_obj_asstring(kasp), file, line);
+ return (ISC_R_EXISTS);
+ }
+
+ /*
+ * Add the new zone plus key-directory.
+ */
+ symkey = isc_mem_strdup(mctx, keydir);
+ symvalue.as_cpointer = zcfg;
+ result = isc_symtab_define(symtab, symkey, 2, symvalue,
+ isc_symexists_reject);
+ return (result);
+}
+
+/*
+ * Check key list for duplicates key names and that the key names
+ * are valid domain names as these keys are used for TSIG.
+ *
+ * Check the key contents for validity.
+ */
+static isc_result_t
+check_keylist(const cfg_obj_t *keys, isc_symtab_t *symtab, isc_mem_t *mctx,
+ isc_log_t *logctx) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *element;
+
+ name = dns_fixedname_initname(&fname);
+ for (element = cfg_list_first(keys); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *key = cfg_listelt_value(element);
+ const char *keyid = cfg_obj_asstring(cfg_map_getname(key));
+ isc_symvalue_t symvalue;
+ isc_buffer_t b;
+ char *keyname;
+
+ isc_buffer_constinit(&b, keyid, strlen(keyid));
+ isc_buffer_add(&b, strlen(keyid));
+ tresult = dns_name_fromtext(name, &b, dns_rootname, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "key '%s': bad key name", keyid);
+ result = tresult;
+ continue;
+ }
+ tresult = bind9_check_key(key, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ return (tresult);
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ keyname = isc_mem_strdup(mctx, namebuf);
+ symvalue.as_cpointer = key;
+ tresult = isc_symtab_define(symtab, keyname, 1, symvalue,
+ isc_symexists_reject);
+ if (tresult == ISC_R_EXISTS) {
+ const char *file;
+ unsigned int line;
+
+ RUNTIME_CHECK(isc_symtab_lookup(symtab, keyname, 1,
+ &symvalue) ==
+ ISC_R_SUCCESS);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "key '%s': already exists "
+ "previous definition: %s:%u",
+ keyid, file, line);
+ isc_mem_free(mctx, keyname);
+ result = tresult;
+ } else if (tresult != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, keyname);
+ return (tresult);
+ }
+ }
+ return (result);
+}
+
+/*
+ * RNDC keys are not normalised unlike TSIG keys.
+ *
+ * "foo." is different to "foo".
+ */
+static bool
+rndckey_exists(const cfg_obj_t *keylist, const char *keyname) {
+ const cfg_listelt_t *element;
+ const cfg_obj_t *obj;
+ const char *str;
+
+ if (keylist == NULL) {
+ return (false);
+ }
+
+ for (element = cfg_list_first(keylist); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ str = cfg_obj_asstring(cfg_map_getname(obj));
+ if (!strcasecmp(str, keyname)) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static struct {
+ const char *v4;
+ const char *v6;
+} sources[] = { { "transfer-source", "transfer-source-v6" },
+ { "notify-source", "notify-source-v6" },
+ { "parental-source", "parental-source-v6" },
+ { "query-source", "query-source-v6" },
+ { NULL, NULL } };
+
+static struct {
+ const char *name;
+ isc_result_t (*set)(dns_peer_t *peer, bool newval);
+} bools[] = {
+ { "bogus", dns_peer_setbogus },
+ { "edns", dns_peer_setsupportedns },
+ { "provide-ixfr", dns_peer_setprovideixfr },
+ { "request-expire", dns_peer_setrequestexpire },
+ { "request-ixfr", dns_peer_setrequestixfr },
+ { "request-nsid", dns_peer_setrequestnsid },
+ { "send-cookie", dns_peer_setsendcookie },
+ { "tcp-keepalive", dns_peer_settcpkeepalive },
+ { "tcp-only", dns_peer_setforcetcp },
+};
+
+static isc_result_t
+check_servers(const cfg_obj_t *config, const cfg_obj_t *voptions,
+ isc_symtab_t *symtab, isc_mem_t *mctx, isc_log_t *logctx) {
+ dns_fixedname_t fname;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ const cfg_listelt_t *e1, *e2;
+ const cfg_obj_t *v1, *v2, *keys;
+ const cfg_obj_t *servers;
+ isc_netaddr_t n1, n2;
+ unsigned int p1, p2;
+ const cfg_obj_t *obj;
+ char buf[ISC_NETADDR_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ const char *xfr;
+ const char *keyval;
+ isc_buffer_t b;
+ int source;
+ dns_name_t *keyname;
+
+ servers = NULL;
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "server", &servers);
+ }
+ if (servers == NULL) {
+ (void)cfg_map_get(config, "server", &servers);
+ }
+ if (servers == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ for (e1 = cfg_list_first(servers); e1 != NULL; e1 = cfg_list_next(e1)) {
+ dns_peer_t *peer = NULL;
+ size_t i;
+ v1 = cfg_listelt_value(e1);
+ cfg_obj_asnetprefix(cfg_map_getname(v1), &n1, &p1);
+ /*
+ * Check that unused bits are zero.
+ */
+ tresult = isc_netaddr_prefixok(&n1, p1);
+ if (tresult != ISC_R_SUCCESS) {
+ INSIST(tresult == ISC_R_FAILURE);
+ isc_netaddr_format(&n1, buf, sizeof(buf));
+ cfg_obj_log(v1, logctx, ISC_LOG_ERROR,
+ "server '%s/%u': invalid prefix "
+ "(extra bits specified)",
+ buf, p1);
+ result = tresult;
+ }
+ source = 0;
+ do {
+ /*
+ * For a v6 server we can't specify a v4 source,
+ * and vice versa.
+ */
+ obj = NULL;
+ if (n1.family == AF_INET) {
+ xfr = sources[source].v6;
+ } else {
+ xfr = sources[source].v4;
+ }
+ (void)cfg_map_get(v1, xfr, &obj);
+ if (obj != NULL) {
+ isc_netaddr_format(&n1, buf, sizeof(buf));
+ cfg_obj_log(v1, logctx, ISC_LOG_ERROR,
+ "server '%s/%u': %s not legal", buf,
+ p1, xfr);
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check that we aren't using the DNS
+ * listener port (i.e. 53, or whatever was set
+ * as "port" in options) as a source port.
+ */
+ obj = NULL;
+ if (n1.family == AF_INET) {
+ xfr = sources[source].v4;
+ } else {
+ xfr = sources[source].v6;
+ }
+ (void)cfg_map_get(v1, xfr, &obj);
+ if (obj != NULL) {
+ const isc_sockaddr_t *sa =
+ cfg_obj_assockaddr(obj);
+ in_port_t port = isc_sockaddr_getport(sa);
+ if (port == dnsport) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'%s' cannot specify the "
+ "DNS listener port (%d)",
+ xfr, port);
+ result = ISC_R_FAILURE;
+ }
+ }
+ } while (sources[++source].v4 != NULL);
+ e2 = e1;
+ while ((e2 = cfg_list_next(e2)) != NULL) {
+ v2 = cfg_listelt_value(e2);
+ cfg_obj_asnetprefix(cfg_map_getname(v2), &n2, &p2);
+ if (p1 == p2 && isc_netaddr_equal(&n1, &n2)) {
+ const char *file = cfg_obj_file(v1);
+ unsigned int line = cfg_obj_line(v1);
+
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+
+ isc_netaddr_format(&n2, buf, sizeof(buf));
+ cfg_obj_log(v2, logctx, ISC_LOG_ERROR,
+ "server '%s/%u': already exists "
+ "previous definition: %s:%u",
+ buf, p2, file, line);
+ result = ISC_R_FAILURE;
+ }
+ }
+ keys = NULL;
+ cfg_map_get(v1, "keys", &keys);
+ if (keys != NULL) {
+ /*
+ * Normalize key name.
+ */
+ keyval = cfg_obj_asstring(keys);
+ isc_buffer_constinit(&b, keyval, strlen(keyval));
+ isc_buffer_add(&b, strlen(keyval));
+ keyname = dns_fixedname_initname(&fname);
+ tresult = dns_name_fromtext(keyname, &b, dns_rootname,
+ 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
+ "bad key name '%s'", keyval);
+ result = ISC_R_FAILURE;
+ continue;
+ }
+ dns_name_format(keyname, namebuf, sizeof(namebuf));
+ tresult = isc_symtab_lookup(symtab, namebuf, 1, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
+ "unknown key '%s'", keyval);
+ result = ISC_R_FAILURE;
+ }
+ }
+ (void)dns_peer_newprefix(mctx, &n1, p1, &peer);
+ for (i = 0; i < ARRAY_SIZE(bools); i++) {
+ const cfg_obj_t *opt = NULL;
+ cfg_map_get(v1, bools[i].name, &opt);
+ if (opt != NULL) {
+ tresult = (bools[i].set)(
+ peer, cfg_obj_asboolean(opt));
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(opt, logctx, ISC_LOG_ERROR,
+ "setting server option "
+ "'%s' failed: %s",
+ bools[i].name,
+ isc_result_totext(tresult));
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ dns_peer_detach(&peer);
+ }
+ return (result);
+}
+
+#define ROOT_KSK_STATIC 0x01
+#define ROOT_KSK_MANAGED 0x02
+#define ROOT_KSK_ANY 0x03
+#define ROOT_KSK_2010 0x04
+#define ROOT_KSK_2017 0x08
+
+static isc_result_t
+check_trust_anchor(const cfg_obj_t *key, bool managed, unsigned int *flagsp,
+ isc_log_t *logctx) {
+ const char *str = NULL, *namestr = NULL;
+ dns_fixedname_t fkeyname;
+ dns_name_t *keyname = NULL;
+ isc_buffer_t b;
+ isc_region_t r;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ uint32_t rdata1, rdata2, rdata3;
+ unsigned char data[4096];
+ const char *atstr = NULL;
+ enum {
+ INIT_DNSKEY,
+ STATIC_DNSKEY,
+ INIT_DS,
+ STATIC_DS,
+ TRUSTED
+ } anchortype;
+
+ /*
+ * The 2010 and 2017 IANA root keys - these are used below
+ * to check the contents of trusted, initial and
+ * static trust anchor configurations.
+ */
+ static const unsigned char root_ksk_2010[] = {
+ 0x03, 0x01, 0x00, 0x01, 0xa8, 0x00, 0x20, 0xa9, 0x55, 0x66,
+ 0xba, 0x42, 0xe8, 0x86, 0xbb, 0x80, 0x4c, 0xda, 0x84, 0xe4,
+ 0x7e, 0xf5, 0x6d, 0xbd, 0x7a, 0xec, 0x61, 0x26, 0x15, 0x55,
+ 0x2c, 0xec, 0x90, 0x6d, 0x21, 0x16, 0xd0, 0xef, 0x20, 0x70,
+ 0x28, 0xc5, 0x15, 0x54, 0x14, 0x4d, 0xfe, 0xaf, 0xe7, 0xc7,
+ 0xcb, 0x8f, 0x00, 0x5d, 0xd1, 0x82, 0x34, 0x13, 0x3a, 0xc0,
+ 0x71, 0x0a, 0x81, 0x18, 0x2c, 0xe1, 0xfd, 0x14, 0xad, 0x22,
+ 0x83, 0xbc, 0x83, 0x43, 0x5f, 0x9d, 0xf2, 0xf6, 0x31, 0x32,
+ 0x51, 0x93, 0x1a, 0x17, 0x6d, 0xf0, 0xda, 0x51, 0xe5, 0x4f,
+ 0x42, 0xe6, 0x04, 0x86, 0x0d, 0xfb, 0x35, 0x95, 0x80, 0x25,
+ 0x0f, 0x55, 0x9c, 0xc5, 0x43, 0xc4, 0xff, 0xd5, 0x1c, 0xbe,
+ 0x3d, 0xe8, 0xcf, 0xd0, 0x67, 0x19, 0x23, 0x7f, 0x9f, 0xc4,
+ 0x7e, 0xe7, 0x29, 0xda, 0x06, 0x83, 0x5f, 0xa4, 0x52, 0xe8,
+ 0x25, 0xe9, 0xa1, 0x8e, 0xbc, 0x2e, 0xcb, 0xcf, 0x56, 0x34,
+ 0x74, 0x65, 0x2c, 0x33, 0xcf, 0x56, 0xa9, 0x03, 0x3b, 0xcd,
+ 0xf5, 0xd9, 0x73, 0x12, 0x17, 0x97, 0xec, 0x80, 0x89, 0x04,
+ 0x1b, 0x6e, 0x03, 0xa1, 0xb7, 0x2d, 0x0a, 0x73, 0x5b, 0x98,
+ 0x4e, 0x03, 0x68, 0x73, 0x09, 0x33, 0x23, 0x24, 0xf2, 0x7c,
+ 0x2d, 0xba, 0x85, 0xe9, 0xdb, 0x15, 0xe8, 0x3a, 0x01, 0x43,
+ 0x38, 0x2e, 0x97, 0x4b, 0x06, 0x21, 0xc1, 0x8e, 0x62, 0x5e,
+ 0xce, 0xc9, 0x07, 0x57, 0x7d, 0x9e, 0x7b, 0xad, 0xe9, 0x52,
+ 0x41, 0xa8, 0x1e, 0xbb, 0xe8, 0xa9, 0x01, 0xd4, 0xd3, 0x27,
+ 0x6e, 0x40, 0xb1, 0x14, 0xc0, 0xa2, 0xe6, 0xfc, 0x38, 0xd1,
+ 0x9c, 0x2e, 0x6a, 0xab, 0x02, 0x64, 0x4b, 0x28, 0x13, 0xf5,
+ 0x75, 0xfc, 0x21, 0x60, 0x1e, 0x0d, 0xee, 0x49, 0xcd, 0x9e,
+ 0xe9, 0x6a, 0x43, 0x10, 0x3e, 0x52, 0x4d, 0x62, 0x87, 0x3d
+ };
+ static const unsigned char root_ksk_2017[] = {
+ 0x03, 0x01, 0x00, 0x01, 0xac, 0xff, 0xb4, 0x09, 0xbc, 0xc9,
+ 0x39, 0xf8, 0x31, 0xf7, 0xa1, 0xe5, 0xec, 0x88, 0xf7, 0xa5,
+ 0x92, 0x55, 0xec, 0x53, 0x04, 0x0b, 0xe4, 0x32, 0x02, 0x73,
+ 0x90, 0xa4, 0xce, 0x89, 0x6d, 0x6f, 0x90, 0x86, 0xf3, 0xc5,
+ 0xe1, 0x77, 0xfb, 0xfe, 0x11, 0x81, 0x63, 0xaa, 0xec, 0x7a,
+ 0xf1, 0x46, 0x2c, 0x47, 0x94, 0x59, 0x44, 0xc4, 0xe2, 0xc0,
+ 0x26, 0xbe, 0x5e, 0x98, 0xbb, 0xcd, 0xed, 0x25, 0x97, 0x82,
+ 0x72, 0xe1, 0xe3, 0xe0, 0x79, 0xc5, 0x09, 0x4d, 0x57, 0x3f,
+ 0x0e, 0x83, 0xc9, 0x2f, 0x02, 0xb3, 0x2d, 0x35, 0x13, 0xb1,
+ 0x55, 0x0b, 0x82, 0x69, 0x29, 0xc8, 0x0d, 0xd0, 0xf9, 0x2c,
+ 0xac, 0x96, 0x6d, 0x17, 0x76, 0x9f, 0xd5, 0x86, 0x7b, 0x64,
+ 0x7c, 0x3f, 0x38, 0x02, 0x9a, 0xbd, 0xc4, 0x81, 0x52, 0xeb,
+ 0x8f, 0x20, 0x71, 0x59, 0xec, 0xc5, 0xd2, 0x32, 0xc7, 0xc1,
+ 0x53, 0x7c, 0x79, 0xf4, 0xb7, 0xac, 0x28, 0xff, 0x11, 0x68,
+ 0x2f, 0x21, 0x68, 0x1b, 0xf6, 0xd6, 0xab, 0xa5, 0x55, 0x03,
+ 0x2b, 0xf6, 0xf9, 0xf0, 0x36, 0xbe, 0xb2, 0xaa, 0xa5, 0xb3,
+ 0x77, 0x8d, 0x6e, 0xeb, 0xfb, 0xa6, 0xbf, 0x9e, 0xa1, 0x91,
+ 0xbe, 0x4a, 0xb0, 0xca, 0xea, 0x75, 0x9e, 0x2f, 0x77, 0x3a,
+ 0x1f, 0x90, 0x29, 0xc7, 0x3e, 0xcb, 0x8d, 0x57, 0x35, 0xb9,
+ 0x32, 0x1d, 0xb0, 0x85, 0xf1, 0xb8, 0xe2, 0xd8, 0x03, 0x8f,
+ 0xe2, 0x94, 0x19, 0x92, 0x54, 0x8c, 0xee, 0x0d, 0x67, 0xdd,
+ 0x45, 0x47, 0xe1, 0x1d, 0xd6, 0x3a, 0xf9, 0xc9, 0xfc, 0x1c,
+ 0x54, 0x66, 0xfb, 0x68, 0x4c, 0xf0, 0x09, 0xd7, 0x19, 0x7c,
+ 0x2c, 0xf7, 0x9e, 0x79, 0x2a, 0xb5, 0x01, 0xe6, 0xa8, 0xa1,
+ 0xca, 0x51, 0x9a, 0xf2, 0xcb, 0x9b, 0x5f, 0x63, 0x67, 0xe9,
+ 0x4c, 0x0d, 0x47, 0x50, 0x24, 0x51, 0x35, 0x7b, 0xe1, 0xb5
+ };
+ static const unsigned char root_ds_1_2017[] = {
+ 0xae, 0x1e, 0xa5, 0xb9, 0x74, 0xd4, 0xc8, 0x58, 0xb7, 0x40,
+ 0xbd, 0x03, 0xe3, 0xce, 0xd7, 0xeb, 0xfc, 0xbd, 0x17, 0x24
+ };
+ static const unsigned char root_ds_2_2017[] = {
+ 0xe0, 0x6d, 0x44, 0xb8, 0x0b, 0x8f, 0x1d, 0x39,
+ 0xa9, 0x5c, 0x0b, 0x0d, 0x7c, 0x65, 0xd0, 0x84,
+ 0x58, 0xe8, 0x80, 0x40, 0x9b, 0xbc, 0x68, 0x34,
+ 0x57, 0x10, 0x42, 0x37, 0xc7, 0xf8, 0xec, 0x8D
+ };
+
+ /* if DNSKEY, flags; if DS, key tag */
+ rdata1 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata1"));
+
+ /* if DNSKEY, protocol; if DS, algorithm */
+ rdata2 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata2"));
+
+ /* if DNSKEY, algorithm; if DS, digest type */
+ rdata3 = cfg_obj_asuint32(cfg_tuple_get(key, "rdata3"));
+
+ namestr = cfg_obj_asstring(cfg_tuple_get(key, "name"));
+
+ keyname = dns_fixedname_initname(&fkeyname);
+ isc_buffer_constinit(&b, namestr, strlen(namestr));
+ isc_buffer_add(&b, strlen(namestr));
+ result = dns_name_fromtext(keyname, &b, dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(key, logctx, ISC_LOG_WARNING, "bad key name: %s\n",
+ isc_result_totext(result));
+ result = ISC_R_FAILURE;
+ }
+
+ if (managed) {
+ atstr = cfg_obj_asstring(cfg_tuple_get(key, "anchortype"));
+
+ if (strcasecmp(atstr, "static-key") == 0) {
+ managed = false;
+ anchortype = STATIC_DNSKEY;
+ } else if (strcasecmp(atstr, "static-ds") == 0) {
+ managed = false;
+ anchortype = STATIC_DS;
+ } else if (strcasecmp(atstr, "initial-key") == 0) {
+ anchortype = INIT_DNSKEY;
+ } else if (strcasecmp(atstr, "initial-ds") == 0) {
+ anchortype = INIT_DS;
+ } else {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "key '%s': "
+ "invalid initialization method '%s'",
+ namestr, atstr);
+ result = ISC_R_FAILURE;
+
+ /*
+ * We can't interpret the trust anchor, so
+ * we skip all other checks.
+ */
+ goto cleanup;
+ }
+ } else {
+ atstr = "trusted-key";
+ anchortype = TRUSTED;
+ }
+
+ switch (anchortype) {
+ case INIT_DNSKEY:
+ case STATIC_DNSKEY:
+ case TRUSTED:
+ if (rdata1 > 0xffff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "flags too big: %u", rdata1);
+ result = ISC_R_RANGE;
+ }
+ if (rdata1 & DNS_KEYFLAG_REVOKE) {
+ cfg_obj_log(key, logctx, ISC_LOG_WARNING,
+ "key flags revoke bit set");
+ }
+ if (rdata2 > 0xff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "protocol too big: %u", rdata2);
+ result = ISC_R_RANGE;
+ }
+ if (rdata3 > 0xff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "algorithm too big: %u\n", rdata3);
+ result = ISC_R_RANGE;
+ }
+
+ isc_buffer_init(&b, data, sizeof(data));
+
+ str = cfg_obj_asstring(cfg_tuple_get(key, "data"));
+ tresult = isc_base64_decodestring(str, &b);
+
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR, "%s",
+ isc_result_totext(tresult));
+ result = ISC_R_FAILURE;
+ } else {
+ isc_buffer_usedregion(&b, &r);
+
+ if ((rdata3 == DST_ALG_RSASHA1) && r.length > 1 &&
+ r.base[0] == 1 && r.base[1] == 3)
+ {
+ cfg_obj_log(key, logctx, ISC_LOG_WARNING,
+ "%s '%s' has a weak exponent",
+ atstr, namestr);
+ }
+ }
+
+ if (result == ISC_R_SUCCESS &&
+ dns_name_equal(keyname, dns_rootname))
+ {
+ /*
+ * Flag any use of a root key, regardless of content.
+ */
+ *flagsp |= (managed ? ROOT_KSK_MANAGED
+ : ROOT_KSK_STATIC);
+
+ if (rdata1 == 257 && rdata2 == 3 && rdata3 == 8 &&
+ (isc_buffer_usedlength(&b) ==
+ sizeof(root_ksk_2010)) &&
+ memcmp(data, root_ksk_2010,
+ sizeof(root_ksk_2010)) == 0)
+ {
+ *flagsp |= ROOT_KSK_2010;
+ }
+
+ if (rdata1 == 257 && rdata2 == 3 && rdata3 == 8 &&
+ (isc_buffer_usedlength(&b) ==
+ sizeof(root_ksk_2017)) &&
+ memcmp(data, root_ksk_2017,
+ sizeof(root_ksk_2017)) == 0)
+ {
+ *flagsp |= ROOT_KSK_2017;
+ }
+ }
+ break;
+
+ case INIT_DS:
+ case STATIC_DS:
+ if (rdata1 > 0xffff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "key tag too big: %u", rdata1);
+ result = ISC_R_RANGE;
+ }
+ if (rdata2 > 0xff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "algorithm too big: %u\n", rdata2);
+ result = ISC_R_RANGE;
+ }
+ if (rdata3 > 0xff) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "digest type too big: %u", rdata3);
+ result = ISC_R_RANGE;
+ }
+
+ isc_buffer_init(&b, data, sizeof(data));
+
+ str = cfg_obj_asstring(cfg_tuple_get(key, "data"));
+ tresult = isc_hex_decodestring(str, &b);
+
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR, "%s",
+ isc_result_totext(tresult));
+ result = ISC_R_FAILURE;
+ }
+ if (result == ISC_R_SUCCESS &&
+ dns_name_equal(keyname, dns_rootname))
+ {
+ /*
+ * Flag any use of a root key, regardless of content.
+ */
+ *flagsp |= (managed ? ROOT_KSK_MANAGED
+ : ROOT_KSK_STATIC);
+
+ if (rdata1 == 20326 && rdata2 == 8 && rdata3 == 1 &&
+ (isc_buffer_usedlength(&b) ==
+ sizeof(root_ds_1_2017)) &&
+ memcmp(data, root_ds_1_2017,
+ sizeof(root_ds_1_2017)) == 0)
+ {
+ *flagsp |= ROOT_KSK_2017;
+ }
+
+ if (rdata1 == 20326 && rdata2 == 8 && rdata3 == 2 &&
+ (isc_buffer_usedlength(&b) ==
+ sizeof(root_ds_2_2017)) &&
+ memcmp(data, root_ds_2_2017,
+ sizeof(root_ds_2_2017)) == 0)
+ {
+ *flagsp |= ROOT_KSK_2017;
+ }
+ }
+ break;
+ }
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+record_static_keys(isc_symtab_t *symtab, isc_mem_t *mctx,
+ const cfg_obj_t *keylist, isc_log_t *logctx,
+ bool autovalidation) {
+ isc_result_t result, ret = ISC_R_SUCCESS;
+ const cfg_listelt_t *elt;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ char namebuf[DNS_NAME_FORMATSIZE], *p = NULL;
+
+ name = dns_fixedname_initname(&fixed);
+
+ for (elt = cfg_list_first(keylist); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const char *initmethod;
+ const cfg_obj_t *init = NULL;
+ const cfg_obj_t *obj = cfg_listelt_value(elt);
+ const char *str = cfg_obj_asstring(cfg_tuple_get(obj, "name"));
+ isc_symvalue_t symvalue;
+
+ result = dns_name_fromstring(name, str, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ init = cfg_tuple_get(obj, "anchortype");
+ if (!cfg_obj_isvoid(init)) {
+ initmethod = cfg_obj_asstring(init);
+ if (strcasecmp(initmethod, "initial-key") == 0) {
+ /* initializing key, skip it */
+ continue;
+ }
+ if (strcasecmp(initmethod, "initial-ds") == 0) {
+ /* initializing key, skip it */
+ continue;
+ }
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ symvalue.as_cpointer = obj;
+ p = isc_mem_strdup(mctx, namebuf);
+ result = isc_symtab_define(symtab, p, 1, symvalue,
+ isc_symexists_reject);
+ if (result == ISC_R_EXISTS) {
+ isc_mem_free(mctx, p);
+ } else if (result != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, p);
+ ret = result;
+ continue;
+ }
+
+ if (autovalidation && dns_name_equal(name, dns_rootname)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "static trust anchor for root zone "
+ "cannot be used with "
+ "'dnssec-validation auto'.");
+ ret = ISC_R_FAILURE;
+ continue;
+ }
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+check_initializing_keys(isc_symtab_t *symtab, const cfg_obj_t *keylist,
+ isc_log_t *logctx) {
+ isc_result_t result, ret = ISC_R_SUCCESS;
+ const cfg_listelt_t *elt;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ name = dns_fixedname_initname(&fixed);
+
+ for (elt = cfg_list_first(keylist); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *obj = cfg_listelt_value(elt);
+ const cfg_obj_t *init = NULL;
+ const char *str;
+ isc_symvalue_t symvalue;
+
+ init = cfg_tuple_get(obj, "anchortype");
+ if (cfg_obj_isvoid(init) ||
+ strcasecmp(cfg_obj_asstring(init), "static-key") == 0 ||
+ strcasecmp(cfg_obj_asstring(init), "static-ds") == 0)
+ {
+ /* static key, skip it */
+ continue;
+ }
+
+ str = cfg_obj_asstring(cfg_tuple_get(obj, "name"));
+ result = dns_name_fromstring(name, str, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ result = isc_symtab_lookup(symtab, namebuf, 1, &symvalue);
+ if (result == ISC_R_SUCCESS) {
+ const char *file = cfg_obj_file(symvalue.as_cpointer);
+ unsigned int line = cfg_obj_line(symvalue.as_cpointer);
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "static and initializing keys "
+ "cannot be used for the "
+ "same domain. "
+ "static key defined at "
+ "%s:%u",
+ file, line);
+
+ ret = ISC_R_FAILURE;
+ }
+ }
+
+ return (ret);
+}
+
+static isc_result_t
+record_ds_keys(isc_symtab_t *symtab, isc_mem_t *mctx,
+ const cfg_obj_t *keylist) {
+ isc_result_t result, ret = ISC_R_SUCCESS;
+ const cfg_listelt_t *elt;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ char namebuf[DNS_NAME_FORMATSIZE], *p = NULL;
+
+ name = dns_fixedname_initname(&fixed);
+
+ for (elt = cfg_list_first(keylist); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const char *initmethod;
+ const cfg_obj_t *init = NULL;
+ const cfg_obj_t *obj = cfg_listelt_value(elt);
+ const char *str = cfg_obj_asstring(cfg_tuple_get(obj, "name"));
+ isc_symvalue_t symvalue;
+
+ result = dns_name_fromstring(name, str, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+
+ init = cfg_tuple_get(obj, "anchortype");
+ if (!cfg_obj_isvoid(init)) {
+ initmethod = cfg_obj_asstring(init);
+ if (strcasecmp(initmethod, "initial-key") == 0 ||
+ strcasecmp(initmethod, "static-key") == 0)
+ {
+ /* Key-style key, skip it */
+ continue;
+ }
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ symvalue.as_cpointer = obj;
+ p = isc_mem_strdup(mctx, namebuf);
+ result = isc_symtab_define(symtab, p, 1, symvalue,
+ isc_symexists_reject);
+ if (result == ISC_R_EXISTS) {
+ isc_mem_free(mctx, p);
+ } else if (result != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, p);
+ ret = result;
+ continue;
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * Check for conflicts between static and initialiizing keys.
+ */
+static isc_result_t
+check_ta_conflicts(const cfg_obj_t *global_ta, const cfg_obj_t *view_ta,
+ const cfg_obj_t *global_tkeys, const cfg_obj_t *view_tkeys,
+ bool autovalidation, isc_mem_t *mctx, isc_log_t *logctx) {
+ isc_result_t result, tresult;
+ const cfg_listelt_t *elt = NULL;
+ const cfg_obj_t *keylist = NULL;
+ isc_symtab_t *statictab = NULL, *dstab = NULL;
+
+ result = isc_symtab_create(mctx, 100, freekey, mctx, false, &statictab);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = isc_symtab_create(mctx, 100, freekey, mctx, false, &dstab);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * First we record all the static keys (i.e., old-style
+ * trusted-keys and trust-anchors configured with "static-key"),
+ * and all the DS-style trust anchors.
+ */
+ for (elt = cfg_list_first(global_ta); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = record_static_keys(statictab, mctx, keylist, logctx,
+ autovalidation);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = record_ds_keys(dstab, mctx, keylist);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ for (elt = cfg_list_first(view_ta); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = record_static_keys(statictab, mctx, keylist, logctx,
+ autovalidation);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = record_ds_keys(dstab, mctx, keylist);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ for (elt = cfg_list_first(global_tkeys); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = record_static_keys(statictab, mctx, keylist, logctx,
+ autovalidation);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ for (elt = cfg_list_first(view_tkeys); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = record_static_keys(statictab, mctx, keylist, logctx,
+ autovalidation);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ /*
+ * Next, ensure that there's no conflict between the
+ * static keys and the trust-anchors configured with "initial-key".
+ */
+ for (elt = cfg_list_first(global_ta); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = check_initializing_keys(statictab, keylist, logctx);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ for (elt = cfg_list_first(view_ta); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ keylist = cfg_listelt_value(elt);
+ tresult = check_initializing_keys(statictab, keylist, logctx);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+cleanup:
+ if (statictab != NULL) {
+ isc_symtab_destroy(&statictab);
+ }
+ if (dstab != NULL) {
+ isc_symtab_destroy(&dstab);
+ }
+ return (result);
+}
+
+typedef enum { special_zonetype_rpz, special_zonetype_catz } special_zonetype_t;
+
+static isc_result_t
+check_rpz_catz(const char *rpz_catz, const cfg_obj_t *rpz_obj,
+ const char *viewname, isc_symtab_t *symtab, isc_log_t *logctx,
+ special_zonetype_t specialzonetype) {
+ const cfg_listelt_t *element;
+ const cfg_obj_t *obj, *nameobj, *zoneobj;
+ const char *zonename, *zonetype;
+ const char *forview = " for view ";
+ isc_symvalue_t value;
+ isc_result_t result, tresult;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ unsigned int num_zones = 0;
+
+ if (viewname == NULL) {
+ viewname = "";
+ forview = "";
+ }
+ result = ISC_R_SUCCESS;
+
+ name = dns_fixedname_initname(&fixed);
+ obj = cfg_tuple_get(rpz_obj, "zone list");
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ obj = cfg_listelt_value(element);
+ nameobj = cfg_tuple_get(obj, "zone name");
+ zonename = cfg_obj_asstring(nameobj);
+ zonetype = "";
+
+ if (specialzonetype == special_zonetype_rpz) {
+ if (++num_zones > 64) {
+ cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR,
+ "more than 64 response policy "
+ "zones in view '%s'",
+ viewname);
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ tresult = dns_name_fromstring(name, zonename, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR,
+ "bad domain name '%s'", zonename);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ continue;
+ }
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ tresult = isc_symtab_lookup(symtab, namebuf, 3, &value);
+ if (tresult == ISC_R_SUCCESS) {
+ obj = NULL;
+ zoneobj = value.as_cpointer;
+ if (zoneobj != NULL && cfg_obj_istuple(zoneobj)) {
+ zoneobj = cfg_tuple_get(zoneobj, "options");
+ }
+ if (zoneobj != NULL && cfg_obj_ismap(zoneobj)) {
+ (void)cfg_map_get(zoneobj, "type", &obj);
+ }
+ if (obj != NULL) {
+ zonetype = cfg_obj_asstring(obj);
+ }
+ }
+ if (strcasecmp(zonetype, "primary") != 0 &&
+ strcasecmp(zonetype, "master") != 0 &&
+ strcasecmp(zonetype, "secondary") != 0 &&
+ strcasecmp(zonetype, "slave") != 0)
+ {
+ cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR,
+ "%s '%s'%s%s is not a primary or secondary "
+ "zone",
+ rpz_catz, zonename, forview, viewname);
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+check_catz(const cfg_obj_t *catz_obj, const char *viewname, isc_mem_t *mctx,
+ isc_log_t *logctx) {
+ const cfg_listelt_t *element;
+ const cfg_obj_t *obj, *nameobj, *primariesobj;
+ const char *zonename;
+ const char *forview = " for view ";
+ isc_result_t result, tresult;
+ isc_symtab_t *symtab = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *name = dns_fixedname_initname(&fixed);
+
+ if (viewname == NULL) {
+ viewname = "";
+ forview = "";
+ }
+
+ result = isc_symtab_create(mctx, 100, freekey, mctx, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ obj = cfg_tuple_get(catz_obj, "zone list");
+
+ for (element = cfg_list_first(obj); element != NULL;
+ element = cfg_list_next(element))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+
+ obj = cfg_listelt_value(element);
+ nameobj = cfg_tuple_get(obj, "zone name");
+ zonename = cfg_obj_asstring(nameobj);
+
+ tresult = dns_name_fromstring(name, zonename, 0, NULL);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "bad domain name '%s'", zonename);
+ if (result == ISC_R_SUCCESS) {
+ result = tresult;
+ continue;
+ }
+ }
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ tresult =
+ nameexist(nameobj, namebuf, 1, symtab,
+ "catalog zone '%s': already added here %s:%u",
+ logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ continue;
+ }
+
+ primariesobj = cfg_tuple_get(obj, "default-primaries");
+ if (primariesobj != NULL && cfg_obj_istuple(primariesobj)) {
+ primariesobj = cfg_tuple_get(obj, "default-masters");
+ if (primariesobj != NULL &&
+ cfg_obj_istuple(primariesobj))
+ {
+ cfg_obj_log(nameobj, logctx, ISC_LOG_ERROR,
+ "catalog zone '%s'%s%s: "
+ "'default-primaries' and "
+ "'default-masters' can not be both "
+ "defined",
+ zonename, forview, viewname);
+ result = ISC_R_FAILURE;
+ break;
+ }
+ }
+ }
+
+ if (symtab != NULL) {
+ isc_symtab_destroy(&symtab);
+ }
+
+ return (result);
+}
+
+/*%
+ * Data structure used for the 'callback_data' argument to check_one_plugin().
+ */
+struct check_one_plugin_data {
+ isc_mem_t *mctx;
+ isc_log_t *lctx;
+ cfg_aclconfctx_t *actx;
+ isc_result_t *check_result;
+};
+
+/*%
+ * A callback for the cfg_pluginlist_foreach() call in check_viewconf() below.
+ * Since the point is to check configuration of all plugins even when
+ * processing some of them fails, always return ISC_R_SUCCESS and indicate any
+ * check failures through the 'check_result' variable passed in via the
+ * 'callback_data' structure.
+ */
+static isc_result_t
+check_one_plugin(const cfg_obj_t *config, const cfg_obj_t *obj,
+ const char *plugin_path, const char *parameters,
+ void *callback_data) {
+ struct check_one_plugin_data *data = callback_data;
+ char full_path[PATH_MAX];
+ isc_result_t result;
+
+ result = ns_plugin_expandpath(plugin_path, full_path,
+ sizeof(full_path));
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, data->lctx, ISC_LOG_ERROR,
+ "%s: plugin check failed: "
+ "unable to get full plugin path: %s",
+ plugin_path, isc_result_totext(result));
+ return (result);
+ }
+
+ result = ns_plugin_check(full_path, parameters, config,
+ cfg_obj_file(obj), cfg_obj_line(obj),
+ data->mctx, data->lctx, data->actx);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, data->lctx, ISC_LOG_ERROR,
+ "%s: plugin check failed: %s", full_path,
+ isc_result_totext(result));
+ *data->check_result = result;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+check_dnstap(const cfg_obj_t *voptions, const cfg_obj_t *config,
+ isc_log_t *logctx) {
+#ifdef HAVE_DNSTAP
+ const cfg_obj_t *options = NULL;
+ const cfg_obj_t *obj = NULL;
+
+ if (config != NULL) {
+ (void)cfg_map_get(config, "options", &options);
+ }
+ if (options != NULL) {
+ (void)cfg_map_get(options, "dnstap-output", &obj);
+ }
+ if (obj == NULL) {
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "dnstap", &obj);
+ }
+ if (options != NULL && obj == NULL) {
+ (void)cfg_map_get(options, "dnstap", &obj);
+ }
+ if (obj != NULL) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "'dnstap-output' must be set if 'dnstap' "
+ "is set");
+ return (ISC_R_FAILURE);
+ }
+ }
+ return (ISC_R_SUCCESS);
+#else /* ifdef HAVE_DNSTAP */
+ UNUSED(voptions);
+ UNUSED(config);
+ UNUSED(logctx);
+
+ return (ISC_R_SUCCESS);
+#endif /* ifdef HAVE_DNSTAP */
+}
+
+static isc_result_t
+check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
+ const char *viewname, dns_rdataclass_t vclass,
+ isc_symtab_t *files, isc_symtab_t *keydirs, bool check_plugins,
+ bool nodeprecate, isc_symtab_t *inview, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ const cfg_obj_t *zones = NULL;
+ const cfg_obj_t *view_tkeys = NULL, *global_tkeys = NULL;
+ const cfg_obj_t *view_mkeys = NULL, *global_mkeys = NULL;
+ const cfg_obj_t *view_ta = NULL, *global_ta = NULL;
+ const cfg_obj_t *check_keys[2] = { NULL, NULL };
+ const cfg_obj_t *keys = NULL;
+ const cfg_listelt_t *element, *element2;
+ isc_symtab_t *symtab = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult = ISC_R_SUCCESS;
+ cfg_aclconfctx_t *actx = NULL;
+ const cfg_obj_t *obj;
+ const cfg_obj_t *options = NULL;
+ const cfg_obj_t *opts = NULL;
+ const cfg_obj_t *plugin_list = NULL;
+ bool autovalidation = false;
+ unsigned int tflags = 0, dflags = 0;
+ int i;
+
+ /*
+ * Get global options block
+ */
+ (void)cfg_map_get(config, "options", &options);
+
+ /*
+ * The most relevant options for this view
+ */
+ if (voptions != NULL) {
+ opts = voptions;
+ } else {
+ opts = options;
+ }
+
+ /*
+ * Check that all zone statements are syntactically correct and
+ * there are no duplicate zones.
+ */
+ tresult = isc_symtab_create(mctx, 1000, freekey, mctx, false, &symtab);
+ if (tresult != ISC_R_SUCCESS) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ cfg_aclconfctx_create(mctx, &actx);
+
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "zone", &zones);
+ } else {
+ (void)cfg_map_get(config, "zone", &zones);
+ }
+
+ for (element = cfg_list_first(zones); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *zone = cfg_listelt_value(element);
+
+ tresult = check_zoneconf(zone, voptions, config, symtab, files,
+ keydirs, inview, viewname, vclass,
+ nodeprecate, actx, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ /*
+ * Check that the response-policy and catalog-zones options
+ * refer to zones that exist.
+ */
+ if (opts != NULL) {
+ obj = NULL;
+ if ((cfg_map_get(opts, "response-policy", &obj) ==
+ ISC_R_SUCCESS) &&
+ (check_rpz_catz("response-policy zone", obj, viewname,
+ symtab, logctx,
+ special_zonetype_rpz) != ISC_R_SUCCESS))
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ obj = NULL;
+ if ((cfg_map_get(opts, "catalog-zones", &obj) ==
+ ISC_R_SUCCESS) &&
+ (check_rpz_catz("catalog zone", obj, viewname, symtab,
+ logctx,
+ special_zonetype_catz) != ISC_R_SUCCESS))
+ {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ /*
+ * Check catalog-zones configuration.
+ */
+ if (opts != NULL) {
+ obj = NULL;
+ if ((cfg_map_get(opts, "catalog-zones", &obj) ==
+ ISC_R_SUCCESS) &&
+ (check_catz(obj, viewname, mctx, logctx) != ISC_R_SUCCESS))
+ {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ isc_symtab_destroy(&symtab);
+
+ /*
+ * Check that forwarding is reasonable.
+ */
+ if (opts != NULL && check_forward(opts, NULL, logctx) != ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check non-zero options at the global and view levels.
+ */
+ if (options != NULL && check_nonzero(options, logctx) != ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+ if (voptions != NULL &&
+ check_nonzero(voptions, logctx) != ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check that dual-stack-servers is reasonable.
+ */
+ if (opts != NULL && check_dual_stack(opts, logctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check that rrset-order is reasonable.
+ */
+ if (opts != NULL && check_order(opts, logctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ /*
+ * Check that all key statements are syntactically correct and
+ * there are no duplicate keys.
+ */
+ tresult = isc_symtab_create(mctx, 1000, freekey, mctx, false, &symtab);
+ if (tresult != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ (void)cfg_map_get(config, "key", &keys);
+ tresult = check_keylist(keys, symtab, mctx, logctx);
+ if (tresult == ISC_R_EXISTS) {
+ result = ISC_R_FAILURE;
+ } else if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+
+ if (voptions != NULL) {
+ keys = NULL;
+ (void)cfg_map_get(voptions, "key", &keys);
+ tresult = check_keylist(keys, symtab, mctx, logctx);
+ if (tresult == ISC_R_EXISTS) {
+ result = ISC_R_FAILURE;
+ } else if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Global servers can refer to keys in views.
+ */
+ if (check_servers(config, voptions, symtab, mctx, logctx) !=
+ ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ isc_symtab_destroy(&symtab);
+
+ /*
+ * Load all DNSSEC keys.
+ */
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "trusted-keys", &view_tkeys);
+ (void)cfg_map_get(voptions, "trust-anchors", &view_ta);
+ (void)cfg_map_get(voptions, "managed-keys", &view_mkeys);
+ }
+ (void)cfg_map_get(config, "trusted-keys", &global_tkeys);
+ (void)cfg_map_get(config, "trust-anchors", &global_ta);
+ (void)cfg_map_get(config, "managed-keys", &global_mkeys);
+
+ /*
+ * Check trusted-keys.
+ */
+ check_keys[0] = view_tkeys;
+ check_keys[1] = global_tkeys;
+ for (i = 0; i < 2; i++) {
+ if (check_keys[i] != NULL) {
+ unsigned int flags = 0;
+
+ for (element = cfg_list_first(check_keys[i]);
+ element != NULL; element = cfg_list_next(element))
+ {
+ const cfg_obj_t *keylist =
+ cfg_listelt_value(element);
+ for (element2 = cfg_list_first(keylist);
+ element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ obj = cfg_listelt_value(element2);
+ tresult = check_trust_anchor(
+ obj, false, &flags, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ if ((flags & ROOT_KSK_STATIC) != 0) {
+ cfg_obj_log(check_keys[i], logctx,
+ ISC_LOG_WARNING,
+ "trusted-keys entry for the root "
+ "zone WILL FAIL after key "
+ "rollover - use trust-anchors "
+ "with initial-key "
+ "or initial-ds instead.");
+ }
+
+ tflags |= flags;
+ }
+ }
+
+ /*
+ * Check dnssec/managed-keys. (Only one or the other can be used.)
+ */
+ if ((view_mkeys != NULL || global_mkeys != NULL) &&
+ (view_ta != NULL || global_ta != NULL))
+ {
+ keys = (view_mkeys != NULL) ? view_mkeys : global_mkeys;
+
+ cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
+ "use of managed-keys is not allowed when "
+ "trust-anchors is also in use");
+ result = ISC_R_FAILURE;
+ }
+
+ if (view_ta == NULL && global_ta == NULL) {
+ view_ta = view_mkeys;
+ global_ta = global_mkeys;
+ }
+
+ check_keys[0] = view_ta;
+ check_keys[1] = global_ta;
+ for (i = 0; i < 2; i++) {
+ if (check_keys[i] != NULL) {
+ unsigned int flags = 0;
+
+ for (element = cfg_list_first(check_keys[i]);
+ element != NULL; element = cfg_list_next(element))
+ {
+ const cfg_obj_t *keylist =
+ cfg_listelt_value(element);
+ for (element2 = cfg_list_first(keylist);
+ element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ obj = cfg_listelt_value(element2);
+ tresult = check_trust_anchor(
+ obj, true, &flags, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+
+ if ((flags & ROOT_KSK_STATIC) != 0) {
+ cfg_obj_log(check_keys[i], logctx,
+ ISC_LOG_WARNING,
+ "static entry for the root "
+ "zone WILL FAIL after key "
+ "rollover - use trust-anchors "
+ "with initial-key "
+ "or initial-ds instead.");
+ }
+
+ if ((flags & ROOT_KSK_2010) != 0 &&
+ (flags & ROOT_KSK_2017) == 0)
+ {
+ cfg_obj_log(check_keys[i], logctx,
+ ISC_LOG_WARNING,
+ "initial-key entry for the root "
+ "zone uses the 2010 key without "
+ "the updated 2017 key");
+ }
+
+ dflags |= flags;
+ }
+ }
+
+ if ((tflags & ROOT_KSK_ANY) != 0 && (dflags & ROOT_KSK_ANY) != 0) {
+ keys = (view_ta != NULL) ? view_ta : global_ta;
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "both trusted-keys and trust-anchors "
+ "for the root zone are present");
+ }
+
+ if ((dflags & ROOT_KSK_ANY) == ROOT_KSK_ANY) {
+ keys = (view_ta != NULL) ? view_ta : global_ta;
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "both initial and static entries for the "
+ "root zone are present");
+ }
+
+ obj = NULL;
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "dnssec-validation", &obj);
+ }
+ if (obj == NULL && options != NULL) {
+ (void)cfg_map_get(options, "dnssec-validation", &obj);
+ }
+ if (obj != NULL && !cfg_obj_isboolean(obj)) {
+ autovalidation = true;
+ }
+
+ tresult = check_ta_conflicts(global_ta, view_ta, global_tkeys,
+ view_tkeys, autovalidation, mctx, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ /*
+ * Check options.
+ */
+ if (voptions != NULL) {
+ tresult = check_options(voptions, NULL, logctx, mctx,
+ optlevel_view);
+ } else {
+ tresult = check_options(config, config, logctx, mctx,
+ optlevel_config);
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = check_dnstap(voptions, config, logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = check_viewacls(actx, voptions, config, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = check_recursionacls(actx, voptions, viewname, config, logctx,
+ mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = check_dns64(actx, voptions, config, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ tresult = check_ratelimit(actx, voptions, config, logctx, mctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+
+ /*
+ * Load plugins.
+ */
+ if (check_plugins) {
+ if (voptions != NULL) {
+ (void)cfg_map_get(voptions, "plugin", &plugin_list);
+ } else {
+ (void)cfg_map_get(config, "plugin", &plugin_list);
+ }
+ }
+
+ {
+ struct check_one_plugin_data check_one_plugin_data = {
+ .mctx = mctx,
+ .lctx = logctx,
+ .actx = actx,
+ .check_result = &tresult,
+ };
+
+ (void)cfg_pluginlist_foreach(config, plugin_list, logctx,
+ check_one_plugin,
+ &check_one_plugin_data);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+cleanup:
+ if (symtab != NULL) {
+ isc_symtab_destroy(&symtab);
+ }
+ if (actx != NULL) {
+ cfg_aclconfctx_detach(&actx);
+ }
+
+ return (result);
+}
+
+static const char *default_channels[] = { "default_syslog", "default_stderr",
+ "default_debug", "null", NULL };
+
+static isc_result_t
+bind9_check_logging(const cfg_obj_t *config, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ const cfg_obj_t *categories = NULL;
+ const cfg_obj_t *category;
+ const cfg_obj_t *channels = NULL;
+ const cfg_obj_t *channel;
+ const cfg_listelt_t *element;
+ const cfg_listelt_t *delement;
+ const char *channelname;
+ const char *catname;
+ const cfg_obj_t *fileobj = NULL;
+ const cfg_obj_t *syslogobj = NULL;
+ const cfg_obj_t *nullobj = NULL;
+ const cfg_obj_t *stderrobj = NULL;
+ const cfg_obj_t *logobj = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ isc_symtab_t *symtab = NULL;
+ isc_symvalue_t symvalue;
+ int i;
+
+ (void)cfg_map_get(config, "logging", &logobj);
+ if (logobj == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = isc_symtab_create(mctx, 100, NULL, NULL, false, &symtab);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ symvalue.as_cpointer = NULL;
+ for (i = 0; default_channels[i] != NULL; i++) {
+ tresult = isc_symtab_define(symtab, default_channels[i], 1,
+ symvalue, isc_symexists_replace);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ cfg_map_get(logobj, "channel", &channels);
+
+ for (element = cfg_list_first(channels); element != NULL;
+ element = cfg_list_next(element))
+ {
+ channel = cfg_listelt_value(element);
+ channelname = cfg_obj_asstring(cfg_map_getname(channel));
+ fileobj = syslogobj = nullobj = stderrobj = NULL;
+ (void)cfg_map_get(channel, "file", &fileobj);
+ (void)cfg_map_get(channel, "syslog", &syslogobj);
+ (void)cfg_map_get(channel, "null", &nullobj);
+ (void)cfg_map_get(channel, "stderr", &stderrobj);
+ i = 0;
+ if (fileobj != NULL) {
+ i++;
+ }
+ if (syslogobj != NULL) {
+ i++;
+ }
+ if (nullobj != NULL) {
+ i++;
+ }
+ if (stderrobj != NULL) {
+ i++;
+ }
+ if (i != 1) {
+ cfg_obj_log(channel, logctx, ISC_LOG_ERROR,
+ "channel '%s': exactly one of file, "
+ "syslog, "
+ "null, and stderr must be present",
+ channelname);
+ result = ISC_R_FAILURE;
+ }
+ tresult = isc_symtab_define(symtab, channelname, 1, symvalue,
+ isc_symexists_replace);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+
+ cfg_map_get(logobj, "category", &categories);
+
+ for (element = cfg_list_first(categories); element != NULL;
+ element = cfg_list_next(element))
+ {
+ category = cfg_listelt_value(element);
+ catname = cfg_obj_asstring(cfg_tuple_get(category, "name"));
+ if (isc_log_categorybyname(logctx, catname) == NULL) {
+ cfg_obj_log(category, logctx, ISC_LOG_ERROR,
+ "undefined category: '%s'", catname);
+ result = ISC_R_FAILURE;
+ }
+ channels = cfg_tuple_get(category, "destinations");
+ for (delement = cfg_list_first(channels); delement != NULL;
+ delement = cfg_list_next(delement))
+ {
+ channel = cfg_listelt_value(delement);
+ channelname = cfg_obj_asstring(channel);
+ tresult = isc_symtab_lookup(symtab, channelname, 1,
+ &symvalue);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(channel, logctx, ISC_LOG_ERROR,
+ "undefined channel: '%s'",
+ channelname);
+ result = tresult;
+ }
+ }
+ }
+ isc_symtab_destroy(&symtab);
+ return (result);
+}
+
+static isc_result_t
+bind9_check_controlskeys(const cfg_obj_t *control, const cfg_obj_t *keylist,
+ isc_log_t *logctx) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_obj_t *control_keylist;
+ const cfg_listelt_t *element;
+ const cfg_obj_t *key;
+ const char *keyval;
+
+ control_keylist = cfg_tuple_get(control, "keys");
+ if (cfg_obj_isvoid(control_keylist)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ for (element = cfg_list_first(control_keylist); element != NULL;
+ element = cfg_list_next(element))
+ {
+ key = cfg_listelt_value(element);
+ keyval = cfg_obj_asstring(key);
+
+ if (!rndckey_exists(keylist, keyval)) {
+ cfg_obj_log(key, logctx, ISC_LOG_ERROR,
+ "unknown key '%s'", keyval);
+ result = ISC_R_NOTFOUND;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+bind9_check_controls(const cfg_obj_t *config, isc_log_t *logctx,
+ isc_mem_t *mctx) {
+ isc_result_t result = ISC_R_SUCCESS, tresult;
+ cfg_aclconfctx_t *actx = NULL;
+ const cfg_listelt_t *element, *element2;
+ const cfg_obj_t *allow;
+ const cfg_obj_t *control;
+ const cfg_obj_t *controls;
+ const cfg_obj_t *controlslist = NULL;
+ const cfg_obj_t *inetcontrols;
+ const cfg_obj_t *unixcontrols;
+ const cfg_obj_t *keylist = NULL;
+ const char *path;
+ uint32_t perm, mask;
+ dns_acl_t *acl = NULL;
+ isc_sockaddr_t addr;
+ int i;
+
+ (void)cfg_map_get(config, "controls", &controlslist);
+ if (controlslist == NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ (void)cfg_map_get(config, "key", &keylist);
+
+ cfg_aclconfctx_create(mctx, &actx);
+
+ /*
+ * INET: Check allow clause.
+ * UNIX: Check "perm" for sanity, check path length.
+ */
+ for (element = cfg_list_first(controlslist); element != NULL;
+ element = cfg_list_next(element))
+ {
+ controls = cfg_listelt_value(element);
+ unixcontrols = NULL;
+ inetcontrols = NULL;
+ (void)cfg_map_get(controls, "unix", &unixcontrols);
+ (void)cfg_map_get(controls, "inet", &inetcontrols);
+ for (element2 = cfg_list_first(inetcontrols); element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ control = cfg_listelt_value(element2);
+ allow = cfg_tuple_get(control, "allow");
+ tresult = cfg_acl_fromconfig(allow, config, logctx,
+ actx, mctx, 0, &acl);
+ if (acl != NULL) {
+ dns_acl_detach(&acl);
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ tresult = bind9_check_controlskeys(control, keylist,
+ logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ for (element2 = cfg_list_first(unixcontrols); element2 != NULL;
+ element2 = cfg_list_next(element2))
+ {
+ control = cfg_listelt_value(element2);
+ path = cfg_obj_asstring(cfg_tuple_get(control, "path"));
+ tresult = isc_sockaddr_frompath(&addr, path);
+ if (tresult == ISC_R_NOSPACE) {
+ cfg_obj_log(control, logctx, ISC_LOG_ERROR,
+ "unix control '%s': path too long",
+ path);
+ result = ISC_R_NOSPACE;
+ }
+ perm = cfg_obj_asuint32(cfg_tuple_get(control, "perm"));
+ for (i = 0; i < 3; i++) {
+#ifdef NEED_SECURE_DIRECTORY
+ mask = (0x1 << (i * 3)); /* SEARCH */
+#else /* ifdef NEED_SECURE_DIRECTORY */
+ mask = (0x6 << (i * 3)); /* READ + WRITE */
+#endif /* ifdef NEED_SECURE_DIRECTORY */
+ if ((perm & mask) == mask) {
+ break;
+ }
+ }
+ if (i == 0) {
+ cfg_obj_log(control, logctx, ISC_LOG_WARNING,
+ "unix control '%s' allows access "
+ "to everyone",
+ path);
+ } else if (i == 3) {
+ cfg_obj_log(control, logctx, ISC_LOG_WARNING,
+ "unix control '%s' allows access "
+ "to nobody",
+ path);
+ }
+ tresult = bind9_check_controlskeys(control, keylist,
+ logctx);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ }
+ }
+ }
+ cfg_aclconfctx_detach(&actx);
+ return (result);
+}
+
+isc_result_t
+bind9_check_namedconf(const cfg_obj_t *config, bool check_plugins,
+ bool nodeprecate, isc_log_t *logctx, isc_mem_t *mctx) {
+ const cfg_obj_t *options = NULL;
+ const cfg_obj_t *views = NULL;
+ const cfg_obj_t *acls = NULL;
+ const cfg_listelt_t *velement;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_result_t tresult;
+ isc_symtab_t *symtab = NULL;
+ isc_symtab_t *files = NULL;
+ isc_symtab_t *keydirs = NULL;
+ isc_symtab_t *inview = NULL;
+
+ static const char *builtin[] = { "localhost", "localnets", "any",
+ "none" };
+
+ (void)cfg_map_get(config, "options", &options);
+
+ if (options != NULL && check_options(options, config, logctx, mctx,
+ optlevel_options) != ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ if (bind9_check_logging(config, logctx, mctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ if (bind9_check_controls(config, logctx, mctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ if (bind9_check_primarylists(config, logctx, mctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+
+ if (bind9_check_parentalagentlists(config, logctx, mctx) !=
+ ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+#if HAVE_LIBNGHTTP2
+ if (bind9_check_httpservers(config, logctx, mctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+#endif /* HAVE_LIBNGHTTP2 */
+
+ if (bind9_check_tls_definitions(config, logctx, mctx) != ISC_R_SUCCESS)
+ {
+ result = ISC_R_FAILURE;
+ }
+
+ (void)cfg_map_get(config, "view", &views);
+
+ if (views != NULL && options != NULL) {
+ if (check_dual_stack(options, logctx) != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+
+ /*
+ * Use case insensitive comparison as not all file
+ * systems are case sensitive. This will prevent people
+ * using FOO.DB and foo.db on case sensitive file
+ * systems but that shouldn't be a major issue.
+ */
+ }
+ }
+
+ /*
+ * Use case insensitive comparison as not all file systems are
+ * case sensitive. This will prevent people using FOO.DB and foo.db
+ * on case sensitive file systems but that shouldn't be a major issue.
+ */
+ tresult = isc_symtab_create(mctx, 100, NULL, NULL, false, &files);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+
+ tresult = isc_symtab_create(mctx, 100, freekey, mctx, false, &keydirs);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+
+ tresult = isc_symtab_create(mctx, 100, freekey, mctx, true, &inview);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+
+ if (views == NULL) {
+ tresult = check_viewconf(config, NULL, NULL, dns_rdataclass_in,
+ files, keydirs, check_plugins,
+ nodeprecate, inview, logctx, mctx);
+ if (result == ISC_R_SUCCESS && tresult != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ } else {
+ const cfg_obj_t *zones = NULL;
+ const cfg_obj_t *plugins = NULL;
+
+ (void)cfg_map_get(config, "zone", &zones);
+ if (zones != NULL) {
+ cfg_obj_log(zones, logctx, ISC_LOG_ERROR,
+ "when using 'view' statements, "
+ "all zones must be in views");
+ result = ISC_R_FAILURE;
+ }
+
+ (void)cfg_map_get(config, "plugin", &plugins);
+ if (plugins != NULL) {
+ cfg_obj_log(plugins, logctx, ISC_LOG_ERROR,
+ "when using 'view' statements, "
+ "all plugins must be defined in views");
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ tresult = isc_symtab_create(mctx, 100, NULL, NULL, true, &symtab);
+ if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ goto cleanup;
+ }
+ for (velement = cfg_list_first(views); velement != NULL;
+ velement = cfg_list_next(velement))
+ {
+ const cfg_obj_t *view = cfg_listelt_value(velement);
+ const cfg_obj_t *vname = cfg_tuple_get(view, "name");
+ const cfg_obj_t *voptions = cfg_tuple_get(view, "options");
+ const cfg_obj_t *vclassobj = cfg_tuple_get(view, "class");
+ dns_rdataclass_t vclass = dns_rdataclass_in;
+ const char *key = cfg_obj_asstring(vname);
+ isc_symvalue_t symvalue;
+ unsigned int symtype;
+
+ tresult = ISC_R_SUCCESS;
+ if (cfg_obj_isstring(vclassobj)) {
+ isc_textregion_t r;
+
+ DE_CONST(cfg_obj_asstring(vclassobj), r.base);
+ r.length = strlen(r.base);
+ tresult = dns_rdataclass_fromtext(&vclass, &r);
+ if (tresult != ISC_R_SUCCESS) {
+ cfg_obj_log(vclassobj, logctx, ISC_LOG_ERROR,
+ "view '%s': invalid class %s",
+ cfg_obj_asstring(vname), r.base);
+ }
+ }
+ symtype = vclass + 1;
+ if (tresult == ISC_R_SUCCESS && symtab != NULL) {
+ symvalue.as_cpointer = view;
+ tresult = isc_symtab_define(symtab, key, symtype,
+ symvalue,
+ isc_symexists_reject);
+ if (tresult == ISC_R_EXISTS) {
+ const char *file;
+ unsigned int line;
+ RUNTIME_CHECK(isc_symtab_lookup(symtab, key,
+ symtype,
+ &symvalue) ==
+ ISC_R_SUCCESS);
+ file = cfg_obj_file(symvalue.as_cpointer);
+ line = cfg_obj_line(symvalue.as_cpointer);
+ cfg_obj_log(view, logctx, ISC_LOG_ERROR,
+ "view '%s': already exists "
+ "previous definition: %s:%u",
+ key, file, line);
+ result = tresult;
+ } else if (tresult != ISC_R_SUCCESS) {
+ result = tresult;
+ } else if ((strcasecmp(key, "_bind") == 0 &&
+ vclass == dns_rdataclass_ch) ||
+ (strcasecmp(key, "_default") == 0 &&
+ vclass == dns_rdataclass_in))
+ {
+ cfg_obj_log(view, logctx, ISC_LOG_ERROR,
+ "attempt to redefine builtin view "
+ "'%s'",
+ key);
+ result = ISC_R_EXISTS;
+ }
+ }
+ if (tresult == ISC_R_SUCCESS) {
+ tresult = check_viewconf(config, voptions, key, vclass,
+ files, keydirs, check_plugins,
+ nodeprecate, inview, logctx,
+ mctx);
+ }
+ if (tresult != ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ }
+
+ cfg_map_get(config, "acl", &acls);
+
+ if (acls != NULL) {
+ const cfg_listelt_t *elt;
+ const cfg_listelt_t *elt2;
+ const char *aclname;
+
+ for (elt = cfg_list_first(acls); elt != NULL;
+ elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *acl = cfg_listelt_value(elt);
+ unsigned int line = cfg_obj_line(acl);
+ unsigned int i;
+
+ aclname = cfg_obj_asstring(cfg_tuple_get(acl, "name"));
+ for (i = 0; i < sizeof(builtin) / sizeof(builtin[0]);
+ i++)
+ {
+ if (strcasecmp(aclname, builtin[i]) == 0) {
+ {
+ cfg_obj_log(acl, logctx,
+ ISC_LOG_ERROR,
+ "attempt to "
+ "redefine "
+ "builtin acl '%s'",
+ aclname);
+ result = ISC_R_FAILURE;
+ break;
+ }
+ }
+ }
+
+ for (elt2 = cfg_list_next(elt); elt2 != NULL;
+ elt2 = cfg_list_next(elt2))
+ {
+ const cfg_obj_t *acl2 = cfg_listelt_value(elt2);
+ const char *name;
+ name = cfg_obj_asstring(
+ cfg_tuple_get(acl2, "name"));
+ if (strcasecmp(aclname, name) == 0) {
+ const char *file = cfg_obj_file(acl);
+
+ if (file == NULL) {
+ file = "<unknown file>";
+ }
+
+ cfg_obj_log(acl2, logctx, ISC_LOG_ERROR,
+ "attempt to redefine "
+ "acl '%s' previous "
+ "definition: %s:%u",
+ name, file, line);
+ result = ISC_R_FAILURE;
+ }
+ }
+ }
+ }
+
+cleanup:
+ if (symtab != NULL) {
+ isc_symtab_destroy(&symtab);
+ }
+ if (inview != NULL) {
+ isc_symtab_destroy(&inview);
+ }
+ if (files != NULL) {
+ isc_symtab_destroy(&files);
+ }
+ if (keydirs != NULL) {
+ isc_symtab_destroy(&keydirs);
+ }
+
+ return (result);
+}
diff --git a/lib/bind9/getaddresses.c b/lib/bind9/getaddresses.c
new file mode 100644
index 0000000..5fa7770
--- /dev/null
+++ b/lib/bind9/getaddresses.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.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/netdb.h>
+#include <isc/netscope.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <bind9/getaddresses.h>
+
+isc_result_t
+bind9_getaddresses(const char *hostname, in_port_t port, isc_sockaddr_t *addrs,
+ int addrsize, int *addrcount) {
+ struct in_addr in4;
+ struct in6_addr in6;
+ bool have_ipv4, have_ipv6;
+ int i;
+
+ struct addrinfo *ai = NULL, *tmpai, hints;
+ int result;
+
+ REQUIRE(hostname != NULL);
+ REQUIRE(addrs != NULL);
+ REQUIRE(addrcount != NULL);
+ REQUIRE(addrsize > 0);
+
+ have_ipv4 = (isc_net_probeipv4() == ISC_R_SUCCESS);
+ have_ipv6 = (isc_net_probeipv6() == ISC_R_SUCCESS);
+
+ /*
+ * Try IPv4, then IPv6. In order to handle the extended format
+ * for IPv6 scoped addresses (address%scope_ID), we'll use a local
+ * working buffer of 128 bytes. The length is an ad-hoc value, but
+ * should be enough for this purpose; the buffer can contain a string
+ * of at least 80 bytes for scope_ID in addition to any IPv6 numeric
+ * addresses (up to 46 bytes), the delimiter character and the
+ * terminating NULL character.
+ */
+ if (inet_pton(AF_INET, hostname, &in4) == 1) {
+ if (have_ipv4) {
+ isc_sockaddr_fromin(&addrs[0], &in4, port);
+ } else {
+ isc_sockaddr_v6fromin(&addrs[0], &in4, port);
+ }
+ *addrcount = 1;
+ return (ISC_R_SUCCESS);
+ } else if (strlen(hostname) <= 127U) {
+ char tmpbuf[128], *d;
+ uint32_t zone = 0;
+
+ strlcpy(tmpbuf, hostname, sizeof(tmpbuf));
+ d = strchr(tmpbuf, '%');
+ if (d != NULL) {
+ *d = '\0';
+ }
+
+ if (inet_pton(AF_INET6, tmpbuf, &in6) == 1) {
+ isc_netaddr_t na;
+
+ if (!have_ipv6) {
+ return (ISC_R_FAMILYNOSUPPORT);
+ }
+
+ if (d != NULL) {
+ isc_result_t iresult;
+
+ iresult = isc_netscope_pton(AF_INET6, d + 1,
+ &in6, &zone);
+
+ if (iresult != ISC_R_SUCCESS) {
+ return (iresult);
+ }
+ }
+
+ isc_netaddr_fromin6(&na, &in6);
+ isc_netaddr_setzone(&na, zone);
+ isc_sockaddr_fromnetaddr(
+ &addrs[0], (const isc_netaddr_t *)&na, port);
+
+ *addrcount = 1;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ memset(&hints, 0, sizeof(hints));
+ if (!have_ipv6) {
+ hints.ai_family = PF_INET;
+ } else if (!have_ipv4) {
+ hints.ai_family = PF_INET6;
+ } else {
+ hints.ai_family = PF_UNSPEC;
+#ifdef AI_ADDRCONFIG
+ hints.ai_flags = AI_ADDRCONFIG;
+#endif /* ifdef AI_ADDRCONFIG */
+ }
+ hints.ai_socktype = SOCK_STREAM;
+#ifdef AI_ADDRCONFIG
+again:
+#endif /* ifdef AI_ADDRCONFIG */
+ result = getaddrinfo(hostname, NULL, &hints, &ai);
+ switch (result) {
+ case 0:
+ break;
+ case EAI_NONAME:
+#if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
+ case EAI_NODATA:
+#endif /* if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME) */
+ return (ISC_R_NOTFOUND);
+#ifdef AI_ADDRCONFIG
+ case EAI_BADFLAGS:
+ if ((hints.ai_flags & AI_ADDRCONFIG) != 0) {
+ hints.ai_flags &= ~AI_ADDRCONFIG;
+ goto again;
+ }
+#endif /* ifdef AI_ADDRCONFIG */
+ FALLTHROUGH;
+ default:
+ return (ISC_R_FAILURE);
+ }
+ for (tmpai = ai, i = 0; tmpai != NULL && i < addrsize;
+ tmpai = tmpai->ai_next)
+ {
+ if (tmpai->ai_family != AF_INET && tmpai->ai_family != AF_INET6)
+ {
+ continue;
+ }
+ if (tmpai->ai_family == AF_INET) {
+ struct sockaddr_in *sin;
+ sin = (struct sockaddr_in *)tmpai->ai_addr;
+ isc_sockaddr_fromin(&addrs[i], &sin->sin_addr, port);
+ } else {
+ struct sockaddr_in6 *sin6;
+ sin6 = (struct sockaddr_in6 *)tmpai->ai_addr;
+ isc_sockaddr_fromin6(&addrs[i], &sin6->sin6_addr, port);
+ }
+ i++;
+ }
+ freeaddrinfo(ai);
+ *addrcount = i;
+ if (*addrcount == 0) {
+ return (ISC_R_NOTFOUND);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
diff --git a/lib/bind9/include/bind9/check.h b/lib/bind9/include/bind9/check.h
new file mode 100644
index 0000000..240170e
--- /dev/null
+++ b/lib/bind9/include/bind9/check.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
+
+/*! \file bind9/check.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <isccfg/cfg.h>
+
+#ifndef MAX_MIN_CACHE_TTL
+#define MAX_MIN_CACHE_TTL 90
+#endif /* MAX_MIN_CACHE_TTL */
+
+#ifndef MAX_MIN_NCACHE_TTL
+#define MAX_MIN_NCACHE_TTL 90
+#endif /* MAX_MIN_NCACHE_TTL */
+
+#ifndef MAX_MAX_NCACHE_TTL
+#define MAX_MAX_NCACHE_TTL 7 * 24 * 3600
+#endif /* MAX_MAX_NCACHE_TTL */
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+bind9_check_namedconf(const cfg_obj_t *config, bool check_plugins,
+ bool nodeprecate, isc_log_t *logctx, isc_mem_t *mctx);
+/*%<
+ * Check the syntactic validity of a configuration parse tree generated from
+ * a named.conf file.
+ *
+ * If 'check_plugins' is true, load plugins and check the validity of their
+ * parameters as well.
+ *
+ * If 'nodeprecate' is true, do not warn about deprecated configuration.
+ *
+ * Requires:
+ *\li config is a valid parse tree
+ *
+ *\li logctx is a valid logging context.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_FAILURE
+ */
+
+isc_result_t
+bind9_check_key(const cfg_obj_t *config, isc_log_t *logctx);
+/*%<
+ * Same as bind9_check_namedconf(), but for a single 'key' statement.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/bind9/include/bind9/getaddresses.h b/lib/bind9/include/bind9/getaddresses.h
new file mode 100644
index 0000000..2904257
--- /dev/null
+++ b/lib/bind9/include/bind9/getaddresses.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file bind9/getaddresses.h */
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+bind9_getaddresses(const char *hostname, in_port_t port, isc_sockaddr_t *addrs,
+ int addrsize, int *addrcount);
+/*%<
+ * Use the system resolver to get the addresses associated with a hostname.
+ * If successful, the number of addresses found is returned in 'addrcount'.
+ * If a hostname lookup is performed and addresses of an unknown family is
+ * seen, it is ignored. If more than 'addrsize' addresses are seen, the
+ * first 'addrsize' are returned and the remainder silently truncated.
+ *
+ * This routine may block. If called by a program using the isc_app
+ * framework, it should be surrounded by isc_app_block()/isc_app_unblock().
+ *
+ * Requires:
+ *\li 'hostname' is not NULL.
+ *\li 'addrs' is not NULL.
+ *\li 'addrsize' > 0
+ *\li 'addrcount' is not NULL.
+ *
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOTFOUND
+ *\li #ISC_R_FAMILYNOSUPPORT - 'hostname' is an IPv6 address, and IPv6 is
+ * not supported.
+ */
+
+ISC_LANG_ENDDECLS
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);
+}
diff --git a/lib/irs/Makefile.am b/lib/irs/Makefile.am
new file mode 100644
index 0000000..85c4065
--- /dev/null
+++ b/lib/irs/Makefile.am
@@ -0,0 +1,27 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libirs.la
+
+libirs_ladir = $(includedir)/irs
+libirs_la_HEADERS = \
+ include/irs/resconf.h
+
+libirs_la_SOURCES = \
+ $(libirs_la_HEADERS) \
+ resconf.c
+
+libirs_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBIRS_CFLAGS)
+
+libirs_la_LIBADD = \
+ $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS) \
+ $(LIBISCCFG_LIBS)
+
+libirs_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
diff --git a/lib/irs/Makefile.in b/lib/irs/Makefile.in
new file mode 100644
index 0000000..66b3575
--- /dev/null
+++ b/lib/irs/Makefile.in
@@ -0,0 +1,889 @@
+# 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
+
+subdir = lib/irs
+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 $(libirs_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)$(libirs_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libirs_la_DEPENDENCIES = $(LIBISC_LIBS) $(LIBDNS_LIBS) \
+ $(LIBISCCFG_LIBS)
+am__objects_1 =
+am_libirs_la_OBJECTS = $(am__objects_1) libirs_la-resconf.lo
+libirs_la_OBJECTS = $(am_libirs_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 =
+libirs_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libirs_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)/libirs_la-resconf.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 = $(libirs_la_SOURCES)
+DIST_SOURCES = $(libirs_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libirs_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 = libirs.la
+libirs_ladir = $(includedir)/irs
+libirs_la_HEADERS = \
+ include/irs/resconf.h
+
+libirs_la_SOURCES = \
+ $(libirs_la_HEADERS) \
+ resconf.c
+
+libirs_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBIRS_CFLAGS)
+
+libirs_la_LIBADD = \
+ $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS) \
+ $(LIBISCCFG_LIBS)
+
+libirs_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: 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/irs/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/irs/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}; \
+ }
+
+libirs.la: $(libirs_la_OBJECTS) $(libirs_la_DEPENDENCIES) $(EXTRA_libirs_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libirs_la_LINK) -rpath $(libdir) $(libirs_la_OBJECTS) $(libirs_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libirs_la-resconf.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 $@ $<
+
+libirs_la-resconf.lo: resconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libirs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libirs_la-resconf.lo -MD -MP -MF $(DEPDIR)/libirs_la-resconf.Tpo -c -o libirs_la-resconf.lo `test -f 'resconf.c' || echo '$(srcdir)/'`resconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libirs_la-resconf.Tpo $(DEPDIR)/libirs_la-resconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='resconf.c' object='libirs_la-resconf.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) $(libirs_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libirs_la-resconf.lo `test -f 'resconf.c' || echo '$(srcdir)/'`resconf.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libirs_laHEADERS: $(libirs_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libirs_la_HEADERS)'; test -n "$(libirs_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libirs_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libirs_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)$(libirs_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libirs_ladir)" || exit $$?; \
+ done
+
+uninstall-libirs_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libirs_la_HEADERS)'; test -n "$(libirs_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libirs_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libirs_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libirs_la-resconf.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-libirs_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)/libirs_la-resconf.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-libLTLIBRARIES uninstall-libirs_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libirs_laHEADERS install-man 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-libLTLIBRARIES uninstall-libirs_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/irs/include/irs/resconf.h b/lib/irs/include/irs/resconf.h
new file mode 100644
index 0000000..3af1723
--- /dev/null
+++ b/lib/irs/include/irs/resconf.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/list.h>
+#include <isc/types.h>
+
+/*! \file
+ *
+ * \brief
+ * The IRS resconf module parses the legacy "/etc/resolv.conf" file and
+ * creates the corresponding configuration objects for the DNS library
+ * modules.
+ */
+
+/*%< resolv.conf configuration information */
+typedef struct irs_resconf irs_resconf_t;
+
+/*%
+ * A DNS search list specified in the 'domain' or 'search' statements
+ * in the "resolv.conf" file.
+ */
+typedef struct irs_resconf_search {
+ char *domain;
+ ISC_LINK(struct irs_resconf_search) link;
+} irs_resconf_search_t;
+
+typedef ISC_LIST(irs_resconf_search_t) irs_resconf_searchlist_t;
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp);
+/*%<
+ * Load the resolver configuration file 'filename' in the "resolv.conf" format,
+ * and create a new irs_resconf_t object from the configuration. If the file
+ * is not found ISC_R_FILENOTFOUND is returned with the structure initialized
+ * as if file contained only:
+ *
+ * nameserver ::1
+ * nameserver 127.0.0.1
+ *
+ * Notes:
+ *
+ *\li Currently, only the following options are supported:
+ * nameserver, domain, search, sortlist, ndots, and options.
+ * In addition, 'sortlist' is not actually effective; it's parsed, but
+ * the application cannot use the configuration.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS on success
+ * \li ISC_R_FILENOTFOUND if the file was not found. *confp will be valid.
+ * \li other on error.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'filename' != NULL
+ *
+ *\li 'confp' != NULL && '*confp' == NULL
+ */
+
+void
+irs_resconf_destroy(irs_resconf_t **confp);
+/*%<
+ * Destroy the resconf object.
+ *
+ * Requires:
+ *
+ *\li '*confp' is a valid resconf object.
+ *
+ * Ensures:
+ *
+ *\li *confp == NULL
+ */
+
+isc_sockaddrlist_t *
+irs_resconf_getnameservers(irs_resconf_t *conf);
+/*%<
+ * Return a list of name server addresses stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid resconf object.
+ */
+
+irs_resconf_searchlist_t *
+irs_resconf_getsearchlist(irs_resconf_t *conf);
+/*%<
+ * Return the search list stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid resconf object.
+ */
+
+unsigned int
+irs_resconf_getndots(irs_resconf_t *conf);
+/*%<
+ * Return the 'ndots' value stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid resconf object.
+ */
+
+unsigned int
+irs_resconf_getattempts(irs_resconf_t *conf);
+/*%<
+ * Return the 'attempts' value stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid resconf object.
+ */
+
+unsigned int
+irs_resconf_gettimeout(irs_resconf_t *conf);
+/*%<
+ * Return the 'timeout' value stored in 'conf'.
+ *
+ * Requires:
+ *
+ *\li 'conf' is a valid resconf object.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/irs/resconf.c b/lib/irs/resconf.c
new file mode 100644
index 0000000..2cd98aa
--- /dev/null
+++ b/lib/irs/resconf.c
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 resconf.c */
+
+/**
+ * Module for parsing resolv.conf files (largely derived from lwconfig.c).
+ *
+ * irs_resconf_load() opens the file filename and parses it to initialize
+ * the configuration structure.
+ *
+ * \section lwconfig_return Return Values
+ *
+ * irs_resconf_load() returns #IRS_R_SUCCESS if it successfully read and
+ * parsed filename. It returns a non-0 error code if filename could not be
+ * opened or contained incorrect resolver statements.
+ *
+ * \section lwconfig_see See Also
+ *
+ * stdio(3), \link resolver resolver \endlink
+ *
+ * \section files Files
+ *
+ * /etc/resolv.conf
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netaddr.h>
+#include <isc/sockaddr.h>
+#include <isc/util.h>
+
+#include <irs/resconf.h>
+
+#define IRS_RESCONF_MAGIC ISC_MAGIC('R', 'E', 'S', 'c')
+#define IRS_RESCONF_VALID(c) ISC_MAGIC_VALID(c, IRS_RESCONF_MAGIC)
+
+/*!
+ * protocol constants
+ */
+
+#if !defined(NS_INADDRSZ)
+#define NS_INADDRSZ 4
+#endif /* if !defined(NS_INADDRSZ) */
+
+#if !defined(NS_IN6ADDRSZ)
+#define NS_IN6ADDRSZ 16
+#endif /* if !defined(NS_IN6ADDRSZ) */
+
+/*!
+ * resolv.conf parameters
+ */
+
+#define RESCONFMAXNAMESERVERS 3U /*%< max 3 "nameserver" entries */
+#define RESCONFMAXSEARCH 8U /*%< max 8 domains in "search" entry */
+#define RESCONFMAXLINELEN 256U /*%< max size of a line */
+#define RESCONFMAXSORTLIST 10U /*%< max 10 */
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+/*!
+ * configuration data structure
+ */
+
+struct irs_resconf {
+ /*
+ * The configuration data is a thread-specific object, and does not
+ * need to be locked.
+ */
+ unsigned int magic;
+ isc_mem_t *mctx;
+
+ isc_sockaddrlist_t nameservers;
+ unsigned int numns; /*%< number of configured servers
+ * */
+
+ char *domainname;
+ char *search[RESCONFMAXSEARCH];
+ uint8_t searchnxt; /*%< index for next free slot
+ * */
+
+ irs_resconf_searchlist_t searchlist;
+
+ struct {
+ isc_netaddr_t addr;
+ /*% mask has a non-zero 'family' if set */
+ isc_netaddr_t mask;
+ } sortlist[RESCONFMAXSORTLIST];
+ uint8_t sortlistnxt;
+
+ /*%< non-zero if 'options debug' set */
+ uint8_t resdebug;
+ /*%< set to n in 'options ndots:n' */
+ uint8_t ndots;
+ /*%< set to n in 'options attempts:n' */
+ uint8_t attempts;
+ /*%< set to n in 'options timeout:n' */
+ uint8_t timeout;
+};
+
+static isc_result_t
+resconf_parsenameserver(irs_resconf_t *conf, FILE *fp);
+static isc_result_t
+resconf_parsedomain(irs_resconf_t *conf, FILE *fp);
+static isc_result_t
+resconf_parsesearch(irs_resconf_t *conf, FILE *fp);
+static isc_result_t
+resconf_parsesortlist(irs_resconf_t *conf, FILE *fp);
+static isc_result_t
+resconf_parseoption(irs_resconf_t *ctx, FILE *fp);
+
+/*!
+ * Eat characters from FP until EOL or EOF. Returns EOF or '\n'
+ */
+static int
+eatline(FILE *fp) {
+ int ch;
+
+ ch = fgetc(fp);
+ while (ch != '\n' && ch != EOF) {
+ ch = fgetc(fp);
+ }
+
+ return (ch);
+}
+
+/*!
+ * Eats white space up to next newline or non-whitespace character (of
+ * EOF). Returns the last character read. Comments are considered white
+ * space.
+ */
+static int
+eatwhite(FILE *fp) {
+ int ch;
+
+ ch = fgetc(fp);
+ while (ch != '\n' && ch != EOF && isspace((unsigned char)ch)) {
+ ch = fgetc(fp);
+ }
+
+ if (ch == ';' || ch == '#') {
+ ch = eatline(fp);
+ }
+
+ return (ch);
+}
+
+/*!
+ * Skip over any leading whitespace and then read in the next sequence of
+ * non-whitespace characters. In this context newline is not considered
+ * whitespace. Returns EOF on end-of-file, or the character
+ * that caused the reading to stop.
+ */
+static int
+getword(FILE *fp, char *buffer, size_t size) {
+ char *p = NULL;
+ int ch;
+
+ REQUIRE(buffer != NULL);
+ REQUIRE(size > 0U);
+
+ p = buffer;
+ *p = '\0';
+
+ ch = eatwhite(fp);
+
+ if (ch == EOF) {
+ return (EOF);
+ }
+
+ do {
+ *p = '\0';
+
+ if (ch == EOF || isspace((unsigned char)ch)) {
+ break;
+ } else if ((size_t)(p - buffer) == size - 1) {
+ return (EOF); /* Not enough space. */
+ }
+
+ *p++ = (char)ch;
+ ch = fgetc(fp);
+ } while (1);
+
+ return (ch);
+}
+
+static isc_result_t
+add_server(isc_mem_t *mctx, const char *address_str,
+ isc_sockaddrlist_t *nameservers) {
+ int error;
+ isc_sockaddr_t *address = NULL;
+ struct addrinfo hints, *res;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ res = NULL;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_protocol = IPPROTO_UDP;
+ hints.ai_flags = AI_NUMERICHOST;
+ error = getaddrinfo(address_str, "53", &hints, &res);
+ if (error != 0) {
+ return (ISC_R_BADADDRESSFORM);
+ }
+
+ /* XXX: special case: treat all-0 IPv4 address as loopback */
+ if (res->ai_family == AF_INET) {
+ struct in_addr *v4;
+ unsigned char zeroaddress[] = { 0, 0, 0, 0 };
+ unsigned char loopaddress[] = { 127, 0, 0, 1 };
+
+ v4 = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
+ if (memcmp(v4, zeroaddress, 4) == 0) {
+ memmove(v4, loopaddress, 4);
+ }
+ }
+
+ address = isc_mem_get(mctx, sizeof(*address));
+ if (res->ai_addrlen > sizeof(address->type)) {
+ isc_mem_put(mctx, address, sizeof(*address));
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ address->length = (unsigned int)res->ai_addrlen;
+ memmove(&address->type.ss, res->ai_addr, res->ai_addrlen);
+ ISC_LINK_INIT(address, link);
+ ISC_LIST_APPEND(*nameservers, address, link);
+
+cleanup:
+ freeaddrinfo(res);
+
+ return (result);
+}
+
+static isc_result_t
+create_addr(const char *buffer, isc_netaddr_t *addr, int convert_zero) {
+ struct in_addr v4;
+ struct in6_addr v6;
+
+ if (inet_pton(AF_INET, buffer, &v4) == 1) {
+ if (convert_zero) {
+ unsigned char zeroaddress[] = { 0, 0, 0, 0 };
+ unsigned char loopaddress[] = { 127, 0, 0, 1 };
+ if (memcmp(&v4, zeroaddress, 4) == 0) {
+ memmove(&v4, loopaddress, 4);
+ }
+ }
+ addr->family = AF_INET;
+ memmove(&addr->type.in, &v4, NS_INADDRSZ);
+ addr->zone = 0;
+ } else if (inet_pton(AF_INET6, buffer, &v6) == 1) {
+ addr->family = AF_INET6;
+ memmove(&addr->type.in6, &v6, NS_IN6ADDRSZ);
+ addr->zone = 0;
+ } else {
+ return (ISC_R_BADADDRESSFORM); /* Unrecognised format. */
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_parsenameserver(irs_resconf_t *conf, FILE *fp) {
+ char word[RESCONFMAXLINELEN];
+ int cp;
+ isc_result_t result;
+
+ cp = getword(fp, word, sizeof(word));
+ if (strlen(word) == 0U) {
+ return (ISC_R_UNEXPECTEDEND); /* Nothing on line. */
+ } else if (cp == ' ' || cp == '\t') {
+ cp = eatwhite(fp);
+ }
+
+ if (cp != EOF && cp != '\n') {
+ return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
+ }
+
+ if (conf->numns == RESCONFMAXNAMESERVERS) {
+ return (ISC_R_SUCCESS);
+ }
+
+ result = add_server(conf->mctx, word, &conf->nameservers);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ conf->numns++;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_parsedomain(irs_resconf_t *conf, FILE *fp) {
+ char word[RESCONFMAXLINELEN];
+ int res;
+ unsigned int i;
+
+ res = getword(fp, word, sizeof(word));
+ if (strlen(word) == 0U) {
+ return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
+ } else if (res == ' ' || res == '\t') {
+ res = eatwhite(fp);
+ }
+
+ if (res != EOF && res != '\n') {
+ return (ISC_R_UNEXPECTEDTOKEN); /* Extra junk on line. */
+ }
+
+ if (conf->domainname != NULL) {
+ isc_mem_free(conf->mctx, conf->domainname);
+ }
+
+ /*
+ * Search and domain are mutually exclusive.
+ */
+ for (i = 0; i < RESCONFMAXSEARCH; i++) {
+ if (conf->search[i] != NULL) {
+ isc_mem_free(conf->mctx, conf->search[i]);
+ conf->search[i] = NULL;
+ }
+ }
+ conf->searchnxt = 0;
+
+ conf->domainname = isc_mem_strdup(conf->mctx, word);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_parsesearch(irs_resconf_t *conf, FILE *fp) {
+ int delim;
+ unsigned int idx;
+ char word[RESCONFMAXLINELEN];
+
+ if (conf->domainname != NULL) {
+ /*
+ * Search and domain are mutually exclusive.
+ */
+ isc_mem_free(conf->mctx, conf->domainname);
+ conf->domainname = NULL;
+ }
+
+ /*
+ * Remove any previous search definitions.
+ */
+ for (idx = 0; idx < RESCONFMAXSEARCH; idx++) {
+ if (conf->search[idx] != NULL) {
+ isc_mem_free(conf->mctx, conf->search[idx]);
+ conf->search[idx] = NULL;
+ }
+ }
+ conf->searchnxt = 0;
+
+ delim = getword(fp, word, sizeof(word));
+ if (strlen(word) == 0U) {
+ return (ISC_R_UNEXPECTEDEND); /* Nothing else on line. */
+ }
+
+ idx = 0;
+ while (strlen(word) > 0U) {
+ if (conf->searchnxt == RESCONFMAXSEARCH) {
+ goto ignore; /* Too many domains. */
+ }
+
+ INSIST(idx < sizeof(conf->search) / sizeof(conf->search[0]));
+ conf->search[idx] = isc_mem_strdup(conf->mctx, word);
+ idx++;
+ conf->searchnxt++;
+
+ ignore:
+ if (delim == EOF || delim == '\n') {
+ break;
+ } else {
+ delim = getword(fp, word, sizeof(word));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_parsesortlist(irs_resconf_t *conf, FILE *fp) {
+ int delim, res;
+ unsigned int idx;
+ char word[RESCONFMAXLINELEN];
+ char *p;
+
+ delim = getword(fp, word, sizeof(word));
+ if (strlen(word) == 0U) {
+ return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
+ }
+
+ while (strlen(word) > 0U) {
+ if (conf->sortlistnxt == RESCONFMAXSORTLIST) {
+ return (ISC_R_QUOTA); /* Too many values. */
+ }
+
+ p = strchr(word, '/');
+ if (p != NULL) {
+ *p++ = '\0';
+ }
+
+ idx = conf->sortlistnxt;
+ INSIST(idx <
+ sizeof(conf->sortlist) / sizeof(conf->sortlist[0]));
+ res = create_addr(word, &conf->sortlist[idx].addr, 1);
+ if (res != ISC_R_SUCCESS) {
+ return (res);
+ }
+
+ if (p != NULL) {
+ res = create_addr(p, &conf->sortlist[idx].mask, 0);
+ if (res != ISC_R_SUCCESS) {
+ return (res);
+ }
+ } else {
+ /*
+ * Make up a mask. (XXX: is this correct?)
+ */
+ conf->sortlist[idx].mask = conf->sortlist[idx].addr;
+ memset(&conf->sortlist[idx].mask.type, 0xff,
+ sizeof(conf->sortlist[idx].mask.type));
+ }
+
+ conf->sortlistnxt++;
+
+ if (delim == EOF || delim == '\n') {
+ break;
+ } else {
+ delim = getword(fp, word, sizeof(word));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_optionnumber(const char *word, uint8_t *number) {
+ char *p;
+ long n;
+
+ n = strtol(word, &p, 10);
+ if (*p != '\0') { /* Bad string. */
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ if (n < 0 || n > 0xff) { /* Out of range. */
+ return (ISC_R_RANGE);
+ }
+ *number = n;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+resconf_parseoption(irs_resconf_t *conf, FILE *fp) {
+ int delim;
+ isc_result_t result = ISC_R_SUCCESS;
+ char word[RESCONFMAXLINELEN];
+
+ delim = getword(fp, word, sizeof(word));
+ if (strlen(word) == 0U) {
+ return (ISC_R_UNEXPECTEDEND); /* Empty line after keyword. */
+ }
+
+ while (strlen(word) > 0U) {
+ if (strcmp("debug", word) == 0) {
+ conf->resdebug = 1;
+ } else if (strncmp("ndots:", word, 6) == 0) {
+ CHECK(resconf_optionnumber(word + 6, &conf->ndots));
+ } else if (strncmp("attempts:", word, 9) == 0) {
+ CHECK(resconf_optionnumber(word + 9, &conf->attempts));
+ } else if (strncmp("timeout:", word, 8) == 0) {
+ CHECK(resconf_optionnumber(word + 8, &conf->timeout));
+ }
+
+ if (delim == EOF || delim == '\n') {
+ break;
+ } else {
+ delim = getword(fp, word, sizeof(word));
+ }
+ }
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+add_search(irs_resconf_t *conf, char *domain) {
+ irs_resconf_search_t *entry;
+
+ entry = isc_mem_get(conf->mctx, sizeof(*entry));
+
+ entry->domain = domain;
+ ISC_LINK_INIT(entry, link);
+ ISC_LIST_APPEND(conf->searchlist, entry, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*% parses a file and fills in the data structure. */
+isc_result_t
+irs_resconf_load(isc_mem_t *mctx, const char *filename, irs_resconf_t **confp) {
+ FILE *fp = NULL;
+ char word[256];
+ isc_result_t rval, ret = ISC_R_SUCCESS;
+ irs_resconf_t *conf;
+ unsigned int i;
+ int stopchar;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(filename != NULL);
+ REQUIRE(strlen(filename) > 0U);
+ REQUIRE(confp != NULL && *confp == NULL);
+
+ conf = isc_mem_get(mctx, sizeof(*conf));
+
+ conf->mctx = mctx;
+ ISC_LIST_INIT(conf->nameservers);
+ ISC_LIST_INIT(conf->searchlist);
+ conf->numns = 0;
+ conf->domainname = NULL;
+ conf->searchnxt = 0;
+ conf->sortlistnxt = 0;
+ conf->resdebug = 0;
+ conf->ndots = 1;
+ conf->attempts = 3;
+ conf->timeout = 0;
+ for (i = 0; i < RESCONFMAXSEARCH; i++) {
+ conf->search[i] = NULL;
+ }
+
+ errno = 0;
+ if ((fp = fopen(filename, "r")) != NULL) {
+ do {
+ stopchar = getword(fp, word, sizeof(word));
+ if (stopchar == EOF) {
+ rval = ISC_R_SUCCESS;
+ POST(rval);
+ break;
+ }
+
+ if (strlen(word) == 0U) {
+ rval = ISC_R_SUCCESS;
+ } else if (strcmp(word, "nameserver") == 0) {
+ rval = resconf_parsenameserver(conf, fp);
+ } else if (strcmp(word, "domain") == 0) {
+ rval = resconf_parsedomain(conf, fp);
+ } else if (strcmp(word, "search") == 0) {
+ rval = resconf_parsesearch(conf, fp);
+ } else if (strcmp(word, "sortlist") == 0) {
+ rval = resconf_parsesortlist(conf, fp);
+ } else if (strcmp(word, "options") == 0) {
+ rval = resconf_parseoption(conf, fp);
+ } else {
+ /* unrecognised word. Ignore entire line */
+ rval = ISC_R_SUCCESS;
+ stopchar = eatline(fp);
+ if (stopchar == EOF) {
+ break;
+ }
+ }
+ if (ret == ISC_R_SUCCESS && rval != ISC_R_SUCCESS) {
+ ret = rval;
+ }
+ } while (1);
+
+ fclose(fp);
+ } else {
+ switch (errno) {
+ case ENOENT:
+ break;
+ default:
+ isc_mem_put(mctx, conf, sizeof(*conf));
+ return (ISC_R_INVALIDFILE);
+ }
+ }
+
+ if (ret != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ /*
+ * Construct unified search list from domain or configured
+ * search list
+ */
+ if (conf->domainname != NULL) {
+ ret = add_search(conf, conf->domainname);
+ } else if (conf->searchnxt > 0) {
+ for (i = 0; i < conf->searchnxt; i++) {
+ ret = add_search(conf, conf->search[i]);
+ if (ret != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ }
+
+ /* If we don't find a nameserver fall back to localhost */
+ if (conf->numns == 0U) {
+ INSIST(ISC_LIST_EMPTY(conf->nameservers));
+
+ /* XXX: should we catch errors? */
+ (void)add_server(conf->mctx, "::1", &conf->nameservers);
+ (void)add_server(conf->mctx, "127.0.0.1", &conf->nameservers);
+ }
+
+error:
+ conf->magic = IRS_RESCONF_MAGIC;
+
+ if (ret != ISC_R_SUCCESS) {
+ irs_resconf_destroy(&conf);
+ } else {
+ if (fp == NULL) {
+ ret = ISC_R_FILENOTFOUND;
+ }
+ *confp = conf;
+ }
+
+ return (ret);
+}
+
+void
+irs_resconf_destroy(irs_resconf_t **confp) {
+ irs_resconf_t *conf;
+ isc_sockaddr_t *address;
+ irs_resconf_search_t *searchentry;
+ unsigned int i;
+
+ REQUIRE(confp != NULL);
+ conf = *confp;
+ *confp = NULL;
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ while ((searchentry = ISC_LIST_HEAD(conf->searchlist)) != NULL) {
+ ISC_LIST_UNLINK(conf->searchlist, searchentry, link);
+ isc_mem_put(conf->mctx, searchentry, sizeof(*searchentry));
+ }
+
+ while ((address = ISC_LIST_HEAD(conf->nameservers)) != NULL) {
+ ISC_LIST_UNLINK(conf->nameservers, address, link);
+ isc_mem_put(conf->mctx, address, sizeof(*address));
+ }
+
+ if (conf->domainname != NULL) {
+ isc_mem_free(conf->mctx, conf->domainname);
+ }
+
+ for (i = 0; i < RESCONFMAXSEARCH; i++) {
+ if (conf->search[i] != NULL) {
+ isc_mem_free(conf->mctx, conf->search[i]);
+ }
+ }
+
+ isc_mem_put(conf->mctx, conf, sizeof(*conf));
+}
+
+isc_sockaddrlist_t *
+irs_resconf_getnameservers(irs_resconf_t *conf) {
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ return (&conf->nameservers);
+}
+
+irs_resconf_searchlist_t *
+irs_resconf_getsearchlist(irs_resconf_t *conf) {
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ return (&conf->searchlist);
+}
+
+unsigned int
+irs_resconf_getndots(irs_resconf_t *conf) {
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ return ((unsigned int)conf->ndots);
+}
+
+unsigned int
+irs_resconf_getattempts(irs_resconf_t *conf) {
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ return ((unsigned int)conf->attempts);
+}
+
+unsigned int
+irs_resconf_gettimeout(irs_resconf_t *conf) {
+ REQUIRE(IRS_RESCONF_VALID(conf));
+
+ return ((unsigned int)conf->timeout);
+}
diff --git a/lib/isc/Makefile.am b/lib/isc/Makefile.am
new file mode 100644
index 0000000..b962f68
--- /dev/null
+++ b/lib/isc/Makefile.am
@@ -0,0 +1,253 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libisc.la
+
+libisc_ladir = $(includedir)/isc
+libisc_la_HEADERS = \
+ include/isc/aes.h \
+ include/isc/align.h \
+ include/isc/app.h \
+ include/isc/assertions.h \
+ include/isc/astack.h \
+ include/isc/atomic.h \
+ include/isc/attributes.h \
+ include/isc/backtrace.h \
+ include/isc/barrier.h \
+ include/isc/base32.h \
+ include/isc/base64.h \
+ include/isc/buffer.h \
+ include/isc/cmocka.h \
+ include/isc/commandline.h \
+ include/isc/condition.h \
+ include/isc/counter.h \
+ include/isc/crc64.h \
+ include/isc/deprecated.h \
+ include/isc/dir.h \
+ include/isc/endian.h \
+ include/isc/errno.h \
+ include/isc/error.h \
+ include/isc/event.h \
+ include/isc/eventclass.h \
+ include/isc/file.h \
+ include/isc/formatcheck.h \
+ include/isc/fuzz.h \
+ include/isc/glob.h \
+ include/isc/hash.h \
+ include/isc/heap.h \
+ include/isc/hex.h \
+ include/isc/hmac.h \
+ include/isc/ht.h \
+ include/isc/httpd.h \
+ include/isc/interfaceiter.h \
+ include/isc/iterated_hash.h \
+ include/isc/lang.h \
+ include/isc/lex.h \
+ include/isc/list.h \
+ include/isc/log.h \
+ include/isc/magic.h \
+ include/isc/managers.h \
+ include/isc/md.h \
+ include/isc/mem.h \
+ include/isc/meminfo.h \
+ include/isc/mutex.h \
+ include/isc/mutexblock.h \
+ include/isc/net.h \
+ include/isc/netaddr.h \
+ include/isc/netdb.h \
+ include/isc/netmgr.h \
+ include/isc/netscope.h \
+ include/isc/nonce.h \
+ include/isc/offset.h \
+ include/isc/once.h \
+ include/isc/os.h \
+ include/isc/parseint.h \
+ include/isc/pool.h \
+ include/isc/portset.h \
+ include/isc/print.h \
+ include/isc/quota.h \
+ include/isc/radix.h \
+ include/isc/random.h \
+ include/isc/ratelimiter.h \
+ include/isc/refcount.h \
+ include/isc/regex.h \
+ include/isc/region.h \
+ include/isc/resource.h \
+ include/isc/result.h \
+ include/isc/rwlock.h \
+ include/isc/safe.h \
+ include/isc/serial.h \
+ include/isc/siphash.h \
+ include/isc/sockaddr.h \
+ include/isc/stat.h \
+ include/isc/stats.h \
+ include/isc/stdatomic.h \
+ include/isc/stdio.h \
+ include/isc/stdtime.h \
+ include/isc/strerr.h \
+ include/isc/string.h \
+ include/isc/symtab.h \
+ include/isc/syslog.h \
+ include/isc/task.h \
+ include/isc/taskpool.h \
+ include/isc/thread.h \
+ include/isc/time.h \
+ include/isc/timer.h \
+ include/isc/tls.h \
+ include/isc/tm.h \
+ include/isc/types.h \
+ include/isc/url.h \
+ include/isc/utf8.h \
+ include/isc/util.h
+
+libisc_la_SOURCES = \
+ $(libisc_la_HEADERS) \
+ netmgr/netmgr-int.h \
+ netmgr/netmgr.c \
+ netmgr/tcp.c \
+ netmgr/tcpdns.c \
+ netmgr/timer.c \
+ netmgr/tlsdns.c \
+ netmgr/udp.c \
+ netmgr/uv-compat.c \
+ netmgr/uv-compat.h \
+ netmgr/uverr2result.c \
+ aes.c \
+ app.c \
+ assertions.c \
+ astack.c \
+ backtrace.c \
+ base32.c \
+ base64.c \
+ buffer.c \
+ commandline.c \
+ condition.c \
+ counter.c \
+ crc64.c \
+ dir.c \
+ entropy.c \
+ entropy_private.h \
+ errno.c \
+ errno2result.c \
+ errno2result.h \
+ error.c \
+ event.c \
+ file.c \
+ glob.c \
+ hash.c \
+ heap.c \
+ hex.c \
+ hmac.c \
+ ht.c \
+ httpd.c \
+ interfaceiter.c \
+ iterated_hash.c \
+ jemalloc_shim.h \
+ lex.c \
+ lib.c \
+ log.c \
+ managers.c \
+ md.c \
+ mem.c \
+ mem_p.h \
+ meminfo.c \
+ mutex.c \
+ mutexblock.c \
+ net.c \
+ netaddr.c \
+ netmgr_p.h \
+ netscope.c \
+ nonce.c \
+ openssl_shim.c \
+ openssl_shim.h \
+ os.c \
+ os_p.h \
+ parseint.c \
+ pool.c \
+ picohttpparser.c \
+ picohttpparser.h \
+ portset.c \
+ quota.c \
+ radix.c \
+ random.c \
+ ratelimiter.c \
+ regex.c \
+ region.c \
+ resource.c \
+ result.c \
+ rwlock.c \
+ safe.c \
+ serial.c \
+ siphash.c \
+ sockaddr.c \
+ stats.c \
+ stdio.c \
+ stdtime.c \
+ string.c \
+ symtab.c \
+ syslog.c \
+ task.c \
+ task_p.h \
+ taskpool.c \
+ thread.c \
+ time.c \
+ timer.c \
+ timer_p.h \
+ tls.c \
+ tls_p.h \
+ tm.c \
+ trampoline.c \
+ trampoline_p.h \
+ url.c \
+ utf8.c
+
+libisc_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBUV_CFLAGS) \
+ $(OPENSSL_CFLAGS) \
+ $(ZLIB_CFLAGS)
+
+libisc_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+libisc_la_LIBADD = \
+ $(LIBUV_LIBS) \
+ $(OPENSSL_LIBS) \
+ $(ZLIB_LIBS)
+
+if HAVE_JEMALLOC
+libisc_la_CPPFLAGS += \
+ $(JEMALLOC_CFLAGS)
+
+libisc_la_LIBADD += \
+ $(JEMALLOC_LIBS)
+endif HAVE_JEMALLOC
+
+if HAVE_JSON_C
+libisc_la_CPPFLAGS += \
+ $(JSON_C_CFLAGS)
+
+libisc_la_LIBADD += \
+ $(JSON_C_LIBS)
+endif HAVE_JSON_C
+
+if HAVE_LIBNGHTTP2
+libisc_la_SOURCES += \
+ netmgr/http.c \
+ netmgr/tlsstream.c
+
+libisc_la_CPPFLAGS += \
+ $(LIBNGHTTP2_CFLAGS)
+
+libisc_la_LIBADD += \
+ $(LIBNGHTTP2_LIBS)
+endif
+
+if HAVE_LIBXML2
+libisc_la_CPPFLAGS += \
+ $(LIBXML2_CFLAGS)
+
+libisc_la_LIBADD += \
+ $(LIBXML2_LIBS)
+endif HAVE_LIBXML2
diff --git a/lib/isc/Makefile.in b/lib/isc/Makefile.in
new file mode 100644
index 0000000..50ec06e
--- /dev/null
+++ b/lib/isc/Makefile.in
@@ -0,0 +1,2073 @@
+# 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_JEMALLOC_TRUE@am__append_2 = \
+@HAVE_JEMALLOC_TRUE@ $(JEMALLOC_CFLAGS)
+
+@HAVE_JEMALLOC_TRUE@am__append_3 = \
+@HAVE_JEMALLOC_TRUE@ $(JEMALLOC_LIBS)
+
+@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_LIBNGHTTP2_TRUE@am__append_6 = \
+@HAVE_LIBNGHTTP2_TRUE@ netmgr/http.c \
+@HAVE_LIBNGHTTP2_TRUE@ netmgr/tlsstream.c
+
+@HAVE_LIBNGHTTP2_TRUE@am__append_7 = \
+@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_CFLAGS)
+
+@HAVE_LIBNGHTTP2_TRUE@am__append_8 = \
+@HAVE_LIBNGHTTP2_TRUE@ $(LIBNGHTTP2_LIBS)
+
+@HAVE_LIBXML2_TRUE@am__append_9 = \
+@HAVE_LIBXML2_TRUE@ $(LIBXML2_CFLAGS)
+
+@HAVE_LIBXML2_TRUE@am__append_10 = \
+@HAVE_LIBXML2_TRUE@ $(LIBXML2_LIBS)
+
+subdir = lib/isc
+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 $(libisc_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)$(libisc_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+@HAVE_JEMALLOC_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1)
+@HAVE_JSON_C_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1)
+@HAVE_LIBNGHTTP2_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1)
+@HAVE_LIBXML2_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1)
+libisc_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) \
+ $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_4) \
+ $(am__DEPENDENCIES_5)
+am__libisc_la_SOURCES_DIST = include/isc/aes.h include/isc/align.h \
+ include/isc/app.h include/isc/assertions.h \
+ include/isc/astack.h include/isc/atomic.h \
+ include/isc/attributes.h include/isc/backtrace.h \
+ include/isc/barrier.h include/isc/base32.h \
+ include/isc/base64.h include/isc/buffer.h include/isc/cmocka.h \
+ include/isc/commandline.h include/isc/condition.h \
+ include/isc/counter.h include/isc/crc64.h \
+ include/isc/deprecated.h include/isc/dir.h \
+ include/isc/endian.h include/isc/errno.h include/isc/error.h \
+ include/isc/event.h include/isc/eventclass.h \
+ include/isc/file.h include/isc/formatcheck.h \
+ include/isc/fuzz.h include/isc/glob.h include/isc/hash.h \
+ include/isc/heap.h include/isc/hex.h include/isc/hmac.h \
+ include/isc/ht.h include/isc/httpd.h \
+ include/isc/interfaceiter.h include/isc/iterated_hash.h \
+ include/isc/lang.h include/isc/lex.h include/isc/list.h \
+ include/isc/log.h include/isc/magic.h include/isc/managers.h \
+ include/isc/md.h include/isc/mem.h include/isc/meminfo.h \
+ include/isc/mutex.h include/isc/mutexblock.h include/isc/net.h \
+ include/isc/netaddr.h include/isc/netdb.h include/isc/netmgr.h \
+ include/isc/netscope.h include/isc/nonce.h \
+ include/isc/offset.h include/isc/once.h include/isc/os.h \
+ include/isc/parseint.h include/isc/pool.h \
+ include/isc/portset.h include/isc/print.h include/isc/quota.h \
+ include/isc/radix.h include/isc/random.h \
+ include/isc/ratelimiter.h include/isc/refcount.h \
+ include/isc/regex.h include/isc/region.h \
+ include/isc/resource.h include/isc/result.h \
+ include/isc/rwlock.h include/isc/safe.h include/isc/serial.h \
+ include/isc/siphash.h include/isc/sockaddr.h \
+ include/isc/stat.h include/isc/stats.h include/isc/stdatomic.h \
+ include/isc/stdio.h include/isc/stdtime.h include/isc/strerr.h \
+ include/isc/string.h include/isc/symtab.h include/isc/syslog.h \
+ include/isc/task.h include/isc/taskpool.h include/isc/thread.h \
+ include/isc/time.h include/isc/timer.h include/isc/tls.h \
+ include/isc/tm.h include/isc/types.h include/isc/url.h \
+ include/isc/utf8.h include/isc/util.h netmgr/netmgr-int.h \
+ netmgr/netmgr.c netmgr/tcp.c netmgr/tcpdns.c netmgr/timer.c \
+ netmgr/tlsdns.c netmgr/udp.c netmgr/uv-compat.c \
+ netmgr/uv-compat.h netmgr/uverr2result.c aes.c app.c \
+ assertions.c astack.c backtrace.c base32.c base64.c buffer.c \
+ commandline.c condition.c counter.c crc64.c dir.c entropy.c \
+ entropy_private.h errno.c errno2result.c errno2result.h \
+ error.c event.c file.c glob.c hash.c heap.c hex.c hmac.c ht.c \
+ httpd.c interfaceiter.c iterated_hash.c jemalloc_shim.h lex.c \
+ lib.c log.c managers.c md.c mem.c mem_p.h meminfo.c mutex.c \
+ mutexblock.c net.c netaddr.c netmgr_p.h netscope.c nonce.c \
+ openssl_shim.c openssl_shim.h os.c os_p.h parseint.c pool.c \
+ picohttpparser.c picohttpparser.h portset.c quota.c radix.c \
+ random.c ratelimiter.c regex.c region.c resource.c result.c \
+ rwlock.c safe.c serial.c siphash.c sockaddr.c stats.c stdio.c \
+ stdtime.c string.c symtab.c syslog.c task.c task_p.h \
+ taskpool.c thread.c time.c timer.c timer_p.h tls.c tls_p.h \
+ tm.c trampoline.c trampoline_p.h url.c utf8.c netmgr/http.c \
+ netmgr/tlsstream.c
+am__objects_1 =
+am__dirstamp = $(am__leading_dot)dirstamp
+@HAVE_LIBNGHTTP2_TRUE@am__objects_2 = netmgr/libisc_la-http.lo \
+@HAVE_LIBNGHTTP2_TRUE@ netmgr/libisc_la-tlsstream.lo
+am_libisc_la_OBJECTS = $(am__objects_1) netmgr/libisc_la-netmgr.lo \
+ netmgr/libisc_la-tcp.lo netmgr/libisc_la-tcpdns.lo \
+ netmgr/libisc_la-timer.lo netmgr/libisc_la-tlsdns.lo \
+ netmgr/libisc_la-udp.lo netmgr/libisc_la-uv-compat.lo \
+ netmgr/libisc_la-uverr2result.lo libisc_la-aes.lo \
+ libisc_la-app.lo libisc_la-assertions.lo libisc_la-astack.lo \
+ libisc_la-backtrace.lo libisc_la-base32.lo libisc_la-base64.lo \
+ libisc_la-buffer.lo libisc_la-commandline.lo \
+ libisc_la-condition.lo libisc_la-counter.lo libisc_la-crc64.lo \
+ libisc_la-dir.lo libisc_la-entropy.lo libisc_la-errno.lo \
+ libisc_la-errno2result.lo libisc_la-error.lo \
+ libisc_la-event.lo libisc_la-file.lo libisc_la-glob.lo \
+ libisc_la-hash.lo libisc_la-heap.lo libisc_la-hex.lo \
+ libisc_la-hmac.lo libisc_la-ht.lo libisc_la-httpd.lo \
+ libisc_la-interfaceiter.lo libisc_la-iterated_hash.lo \
+ libisc_la-lex.lo libisc_la-lib.lo libisc_la-log.lo \
+ libisc_la-managers.lo libisc_la-md.lo libisc_la-mem.lo \
+ libisc_la-meminfo.lo libisc_la-mutex.lo \
+ libisc_la-mutexblock.lo libisc_la-net.lo libisc_la-netaddr.lo \
+ libisc_la-netscope.lo libisc_la-nonce.lo \
+ libisc_la-openssl_shim.lo libisc_la-os.lo \
+ libisc_la-parseint.lo libisc_la-pool.lo \
+ libisc_la-picohttpparser.lo libisc_la-portset.lo \
+ libisc_la-quota.lo libisc_la-radix.lo libisc_la-random.lo \
+ libisc_la-ratelimiter.lo libisc_la-regex.lo \
+ libisc_la-region.lo libisc_la-resource.lo libisc_la-result.lo \
+ libisc_la-rwlock.lo libisc_la-safe.lo libisc_la-serial.lo \
+ libisc_la-siphash.lo libisc_la-sockaddr.lo libisc_la-stats.lo \
+ libisc_la-stdio.lo libisc_la-stdtime.lo libisc_la-string.lo \
+ libisc_la-symtab.lo libisc_la-syslog.lo libisc_la-task.lo \
+ libisc_la-taskpool.lo libisc_la-thread.lo libisc_la-time.lo \
+ libisc_la-timer.lo libisc_la-tls.lo libisc_la-tm.lo \
+ libisc_la-trampoline.lo libisc_la-url.lo libisc_la-utf8.lo \
+ $(am__objects_2)
+libisc_la_OBJECTS = $(am_libisc_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 =
+libisc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libisc_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)/libisc_la-aes.Plo \
+ ./$(DEPDIR)/libisc_la-app.Plo \
+ ./$(DEPDIR)/libisc_la-assertions.Plo \
+ ./$(DEPDIR)/libisc_la-astack.Plo \
+ ./$(DEPDIR)/libisc_la-backtrace.Plo \
+ ./$(DEPDIR)/libisc_la-base32.Plo \
+ ./$(DEPDIR)/libisc_la-base64.Plo \
+ ./$(DEPDIR)/libisc_la-buffer.Plo \
+ ./$(DEPDIR)/libisc_la-commandline.Plo \
+ ./$(DEPDIR)/libisc_la-condition.Plo \
+ ./$(DEPDIR)/libisc_la-counter.Plo \
+ ./$(DEPDIR)/libisc_la-crc64.Plo ./$(DEPDIR)/libisc_la-dir.Plo \
+ ./$(DEPDIR)/libisc_la-entropy.Plo \
+ ./$(DEPDIR)/libisc_la-errno.Plo \
+ ./$(DEPDIR)/libisc_la-errno2result.Plo \
+ ./$(DEPDIR)/libisc_la-error.Plo \
+ ./$(DEPDIR)/libisc_la-event.Plo ./$(DEPDIR)/libisc_la-file.Plo \
+ ./$(DEPDIR)/libisc_la-glob.Plo ./$(DEPDIR)/libisc_la-hash.Plo \
+ ./$(DEPDIR)/libisc_la-heap.Plo ./$(DEPDIR)/libisc_la-hex.Plo \
+ ./$(DEPDIR)/libisc_la-hmac.Plo ./$(DEPDIR)/libisc_la-ht.Plo \
+ ./$(DEPDIR)/libisc_la-httpd.Plo \
+ ./$(DEPDIR)/libisc_la-interfaceiter.Plo \
+ ./$(DEPDIR)/libisc_la-iterated_hash.Plo \
+ ./$(DEPDIR)/libisc_la-lex.Plo ./$(DEPDIR)/libisc_la-lib.Plo \
+ ./$(DEPDIR)/libisc_la-log.Plo \
+ ./$(DEPDIR)/libisc_la-managers.Plo \
+ ./$(DEPDIR)/libisc_la-md.Plo ./$(DEPDIR)/libisc_la-mem.Plo \
+ ./$(DEPDIR)/libisc_la-meminfo.Plo \
+ ./$(DEPDIR)/libisc_la-mutex.Plo \
+ ./$(DEPDIR)/libisc_la-mutexblock.Plo \
+ ./$(DEPDIR)/libisc_la-net.Plo \
+ ./$(DEPDIR)/libisc_la-netaddr.Plo \
+ ./$(DEPDIR)/libisc_la-netscope.Plo \
+ ./$(DEPDIR)/libisc_la-nonce.Plo \
+ ./$(DEPDIR)/libisc_la-openssl_shim.Plo \
+ ./$(DEPDIR)/libisc_la-os.Plo \
+ ./$(DEPDIR)/libisc_la-parseint.Plo \
+ ./$(DEPDIR)/libisc_la-picohttpparser.Plo \
+ ./$(DEPDIR)/libisc_la-pool.Plo \
+ ./$(DEPDIR)/libisc_la-portset.Plo \
+ ./$(DEPDIR)/libisc_la-quota.Plo \
+ ./$(DEPDIR)/libisc_la-radix.Plo \
+ ./$(DEPDIR)/libisc_la-random.Plo \
+ ./$(DEPDIR)/libisc_la-ratelimiter.Plo \
+ ./$(DEPDIR)/libisc_la-regex.Plo \
+ ./$(DEPDIR)/libisc_la-region.Plo \
+ ./$(DEPDIR)/libisc_la-resource.Plo \
+ ./$(DEPDIR)/libisc_la-result.Plo \
+ ./$(DEPDIR)/libisc_la-rwlock.Plo \
+ ./$(DEPDIR)/libisc_la-safe.Plo \
+ ./$(DEPDIR)/libisc_la-serial.Plo \
+ ./$(DEPDIR)/libisc_la-siphash.Plo \
+ ./$(DEPDIR)/libisc_la-sockaddr.Plo \
+ ./$(DEPDIR)/libisc_la-stats.Plo \
+ ./$(DEPDIR)/libisc_la-stdio.Plo \
+ ./$(DEPDIR)/libisc_la-stdtime.Plo \
+ ./$(DEPDIR)/libisc_la-string.Plo \
+ ./$(DEPDIR)/libisc_la-symtab.Plo \
+ ./$(DEPDIR)/libisc_la-syslog.Plo \
+ ./$(DEPDIR)/libisc_la-task.Plo \
+ ./$(DEPDIR)/libisc_la-taskpool.Plo \
+ ./$(DEPDIR)/libisc_la-thread.Plo \
+ ./$(DEPDIR)/libisc_la-time.Plo ./$(DEPDIR)/libisc_la-timer.Plo \
+ ./$(DEPDIR)/libisc_la-tls.Plo ./$(DEPDIR)/libisc_la-tm.Plo \
+ ./$(DEPDIR)/libisc_la-trampoline.Plo \
+ ./$(DEPDIR)/libisc_la-url.Plo ./$(DEPDIR)/libisc_la-utf8.Plo \
+ netmgr/$(DEPDIR)/libisc_la-http.Plo \
+ netmgr/$(DEPDIR)/libisc_la-netmgr.Plo \
+ netmgr/$(DEPDIR)/libisc_la-tcp.Plo \
+ netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo \
+ netmgr/$(DEPDIR)/libisc_la-timer.Plo \
+ netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo \
+ netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo \
+ netmgr/$(DEPDIR)/libisc_la-udp.Plo \
+ netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo \
+ netmgr/$(DEPDIR)/libisc_la-uverr2result.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 = $(libisc_la_SOURCES)
+DIST_SOURCES = $(am__libisc_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 = $(libisc_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 = libisc.la
+libisc_ladir = $(includedir)/isc
+libisc_la_HEADERS = \
+ include/isc/aes.h \
+ include/isc/align.h \
+ include/isc/app.h \
+ include/isc/assertions.h \
+ include/isc/astack.h \
+ include/isc/atomic.h \
+ include/isc/attributes.h \
+ include/isc/backtrace.h \
+ include/isc/barrier.h \
+ include/isc/base32.h \
+ include/isc/base64.h \
+ include/isc/buffer.h \
+ include/isc/cmocka.h \
+ include/isc/commandline.h \
+ include/isc/condition.h \
+ include/isc/counter.h \
+ include/isc/crc64.h \
+ include/isc/deprecated.h \
+ include/isc/dir.h \
+ include/isc/endian.h \
+ include/isc/errno.h \
+ include/isc/error.h \
+ include/isc/event.h \
+ include/isc/eventclass.h \
+ include/isc/file.h \
+ include/isc/formatcheck.h \
+ include/isc/fuzz.h \
+ include/isc/glob.h \
+ include/isc/hash.h \
+ include/isc/heap.h \
+ include/isc/hex.h \
+ include/isc/hmac.h \
+ include/isc/ht.h \
+ include/isc/httpd.h \
+ include/isc/interfaceiter.h \
+ include/isc/iterated_hash.h \
+ include/isc/lang.h \
+ include/isc/lex.h \
+ include/isc/list.h \
+ include/isc/log.h \
+ include/isc/magic.h \
+ include/isc/managers.h \
+ include/isc/md.h \
+ include/isc/mem.h \
+ include/isc/meminfo.h \
+ include/isc/mutex.h \
+ include/isc/mutexblock.h \
+ include/isc/net.h \
+ include/isc/netaddr.h \
+ include/isc/netdb.h \
+ include/isc/netmgr.h \
+ include/isc/netscope.h \
+ include/isc/nonce.h \
+ include/isc/offset.h \
+ include/isc/once.h \
+ include/isc/os.h \
+ include/isc/parseint.h \
+ include/isc/pool.h \
+ include/isc/portset.h \
+ include/isc/print.h \
+ include/isc/quota.h \
+ include/isc/radix.h \
+ include/isc/random.h \
+ include/isc/ratelimiter.h \
+ include/isc/refcount.h \
+ include/isc/regex.h \
+ include/isc/region.h \
+ include/isc/resource.h \
+ include/isc/result.h \
+ include/isc/rwlock.h \
+ include/isc/safe.h \
+ include/isc/serial.h \
+ include/isc/siphash.h \
+ include/isc/sockaddr.h \
+ include/isc/stat.h \
+ include/isc/stats.h \
+ include/isc/stdatomic.h \
+ include/isc/stdio.h \
+ include/isc/stdtime.h \
+ include/isc/strerr.h \
+ include/isc/string.h \
+ include/isc/symtab.h \
+ include/isc/syslog.h \
+ include/isc/task.h \
+ include/isc/taskpool.h \
+ include/isc/thread.h \
+ include/isc/time.h \
+ include/isc/timer.h \
+ include/isc/tls.h \
+ include/isc/tm.h \
+ include/isc/types.h \
+ include/isc/url.h \
+ include/isc/utf8.h \
+ include/isc/util.h
+
+libisc_la_SOURCES = $(libisc_la_HEADERS) netmgr/netmgr-int.h \
+ netmgr/netmgr.c netmgr/tcp.c netmgr/tcpdns.c netmgr/timer.c \
+ netmgr/tlsdns.c netmgr/udp.c netmgr/uv-compat.c \
+ netmgr/uv-compat.h netmgr/uverr2result.c aes.c app.c \
+ assertions.c astack.c backtrace.c base32.c base64.c buffer.c \
+ commandline.c condition.c counter.c crc64.c dir.c entropy.c \
+ entropy_private.h errno.c errno2result.c errno2result.h \
+ error.c event.c file.c glob.c hash.c heap.c hex.c hmac.c ht.c \
+ httpd.c interfaceiter.c iterated_hash.c jemalloc_shim.h lex.c \
+ lib.c log.c managers.c md.c mem.c mem_p.h meminfo.c mutex.c \
+ mutexblock.c net.c netaddr.c netmgr_p.h netscope.c nonce.c \
+ openssl_shim.c openssl_shim.h os.c os_p.h parseint.c pool.c \
+ picohttpparser.c picohttpparser.h portset.c quota.c radix.c \
+ random.c ratelimiter.c regex.c region.c resource.c result.c \
+ rwlock.c safe.c serial.c siphash.c sockaddr.c stats.c stdio.c \
+ stdtime.c string.c symtab.c syslog.c task.c task_p.h \
+ taskpool.c thread.c time.c timer.c timer_p.h tls.c tls_p.h \
+ tm.c trampoline.c trampoline_p.h url.c utf8.c $(am__append_6)
+libisc_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBISC_CFLAGS) $(LIBUV_CFLAGS) \
+ $(OPENSSL_CFLAGS) $(ZLIB_CFLAGS) $(am__append_2) \
+ $(am__append_4) $(am__append_7) $(am__append_9)
+libisc_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+libisc_la_LIBADD = $(LIBUV_LIBS) $(OPENSSL_LIBS) $(ZLIB_LIBS) \
+ $(am__append_3) $(am__append_5) $(am__append_8) \
+ $(am__append_10)
+all: 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/isc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/isc/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}; \
+ }
+netmgr/$(am__dirstamp):
+ @$(MKDIR_P) netmgr
+ @: > netmgr/$(am__dirstamp)
+netmgr/$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) netmgr/$(DEPDIR)
+ @: > netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-netmgr.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-tcp.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-tcpdns.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-timer.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-tlsdns.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-udp.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-uv-compat.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-uverr2result.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-http.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+netmgr/libisc_la-tlsstream.lo: netmgr/$(am__dirstamp) \
+ netmgr/$(DEPDIR)/$(am__dirstamp)
+
+libisc.la: $(libisc_la_OBJECTS) $(libisc_la_DEPENDENCIES) $(EXTRA_libisc_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libisc_la_LINK) -rpath $(libdir) $(libisc_la_OBJECTS) $(libisc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f netmgr/*.$(OBJEXT)
+ -rm -f netmgr/*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-aes.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-app.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-assertions.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-astack.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-backtrace.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-base32.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-base64.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-buffer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-commandline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-condition.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-counter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-crc64.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-dir.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-entropy.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-errno.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-errno2result.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-error.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-event.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-file.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-glob.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-heap.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-hmac.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-ht.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-httpd.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-interfaceiter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-iterated_hash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-lex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-lib.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-managers.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-md.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mem.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-meminfo.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mutex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-mutexblock.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-net.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-netaddr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-netscope.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-nonce.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-openssl_shim.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-os.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-parseint.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-picohttpparser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-pool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-portset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-quota.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-radix.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-random.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-ratelimiter.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-regex.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-region.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-resource.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-result.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-rwlock.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-safe.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-serial.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-siphash.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-sockaddr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stdio.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-stdtime.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-string.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-symtab.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-syslog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-task.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-taskpool.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-thread.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-time.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-timer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-tls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-tm.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-trampoline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-url.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisc_la-utf8.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-http.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-netmgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tcp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-timer.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-udp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@netmgr/$(DEPDIR)/libisc_la-uverr2result.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 $@ $<
+
+netmgr/libisc_la-netmgr.lo: netmgr/netmgr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-netmgr.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-netmgr.Tpo -c -o netmgr/libisc_la-netmgr.lo `test -f 'netmgr/netmgr.c' || echo '$(srcdir)/'`netmgr/netmgr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-netmgr.Tpo netmgr/$(DEPDIR)/libisc_la-netmgr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/netmgr.c' object='netmgr/libisc_la-netmgr.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-netmgr.lo `test -f 'netmgr/netmgr.c' || echo '$(srcdir)/'`netmgr/netmgr.c
+
+netmgr/libisc_la-tcp.lo: netmgr/tcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tcp.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tcp.Tpo -c -o netmgr/libisc_la-tcp.lo `test -f 'netmgr/tcp.c' || echo '$(srcdir)/'`netmgr/tcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tcp.Tpo netmgr/$(DEPDIR)/libisc_la-tcp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tcp.c' object='netmgr/libisc_la-tcp.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tcp.lo `test -f 'netmgr/tcp.c' || echo '$(srcdir)/'`netmgr/tcp.c
+
+netmgr/libisc_la-tcpdns.lo: netmgr/tcpdns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tcpdns.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tcpdns.Tpo -c -o netmgr/libisc_la-tcpdns.lo `test -f 'netmgr/tcpdns.c' || echo '$(srcdir)/'`netmgr/tcpdns.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tcpdns.Tpo netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tcpdns.c' object='netmgr/libisc_la-tcpdns.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tcpdns.lo `test -f 'netmgr/tcpdns.c' || echo '$(srcdir)/'`netmgr/tcpdns.c
+
+netmgr/libisc_la-timer.lo: netmgr/timer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-timer.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-timer.Tpo -c -o netmgr/libisc_la-timer.lo `test -f 'netmgr/timer.c' || echo '$(srcdir)/'`netmgr/timer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-timer.Tpo netmgr/$(DEPDIR)/libisc_la-timer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/timer.c' object='netmgr/libisc_la-timer.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-timer.lo `test -f 'netmgr/timer.c' || echo '$(srcdir)/'`netmgr/timer.c
+
+netmgr/libisc_la-tlsdns.lo: netmgr/tlsdns.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tlsdns.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tlsdns.Tpo -c -o netmgr/libisc_la-tlsdns.lo `test -f 'netmgr/tlsdns.c' || echo '$(srcdir)/'`netmgr/tlsdns.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tlsdns.Tpo netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tlsdns.c' object='netmgr/libisc_la-tlsdns.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tlsdns.lo `test -f 'netmgr/tlsdns.c' || echo '$(srcdir)/'`netmgr/tlsdns.c
+
+netmgr/libisc_la-udp.lo: netmgr/udp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-udp.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-udp.Tpo -c -o netmgr/libisc_la-udp.lo `test -f 'netmgr/udp.c' || echo '$(srcdir)/'`netmgr/udp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-udp.Tpo netmgr/$(DEPDIR)/libisc_la-udp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/udp.c' object='netmgr/libisc_la-udp.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-udp.lo `test -f 'netmgr/udp.c' || echo '$(srcdir)/'`netmgr/udp.c
+
+netmgr/libisc_la-uv-compat.lo: netmgr/uv-compat.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-uv-compat.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-uv-compat.Tpo -c -o netmgr/libisc_la-uv-compat.lo `test -f 'netmgr/uv-compat.c' || echo '$(srcdir)/'`netmgr/uv-compat.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-uv-compat.Tpo netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/uv-compat.c' object='netmgr/libisc_la-uv-compat.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-uv-compat.lo `test -f 'netmgr/uv-compat.c' || echo '$(srcdir)/'`netmgr/uv-compat.c
+
+netmgr/libisc_la-uverr2result.lo: netmgr/uverr2result.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-uverr2result.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-uverr2result.Tpo -c -o netmgr/libisc_la-uverr2result.lo `test -f 'netmgr/uverr2result.c' || echo '$(srcdir)/'`netmgr/uverr2result.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-uverr2result.Tpo netmgr/$(DEPDIR)/libisc_la-uverr2result.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/uverr2result.c' object='netmgr/libisc_la-uverr2result.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-uverr2result.lo `test -f 'netmgr/uverr2result.c' || echo '$(srcdir)/'`netmgr/uverr2result.c
+
+libisc_la-aes.lo: aes.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-aes.lo -MD -MP -MF $(DEPDIR)/libisc_la-aes.Tpo -c -o libisc_la-aes.lo `test -f 'aes.c' || echo '$(srcdir)/'`aes.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-aes.Tpo $(DEPDIR)/libisc_la-aes.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='aes.c' object='libisc_la-aes.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-aes.lo `test -f 'aes.c' || echo '$(srcdir)/'`aes.c
+
+libisc_la-app.lo: app.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-app.lo -MD -MP -MF $(DEPDIR)/libisc_la-app.Tpo -c -o libisc_la-app.lo `test -f 'app.c' || echo '$(srcdir)/'`app.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-app.Tpo $(DEPDIR)/libisc_la-app.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='app.c' object='libisc_la-app.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-app.lo `test -f 'app.c' || echo '$(srcdir)/'`app.c
+
+libisc_la-assertions.lo: assertions.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-assertions.lo -MD -MP -MF $(DEPDIR)/libisc_la-assertions.Tpo -c -o libisc_la-assertions.lo `test -f 'assertions.c' || echo '$(srcdir)/'`assertions.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-assertions.Tpo $(DEPDIR)/libisc_la-assertions.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='assertions.c' object='libisc_la-assertions.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-assertions.lo `test -f 'assertions.c' || echo '$(srcdir)/'`assertions.c
+
+libisc_la-astack.lo: astack.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-astack.lo -MD -MP -MF $(DEPDIR)/libisc_la-astack.Tpo -c -o libisc_la-astack.lo `test -f 'astack.c' || echo '$(srcdir)/'`astack.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-astack.Tpo $(DEPDIR)/libisc_la-astack.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='astack.c' object='libisc_la-astack.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-astack.lo `test -f 'astack.c' || echo '$(srcdir)/'`astack.c
+
+libisc_la-backtrace.lo: backtrace.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-backtrace.lo -MD -MP -MF $(DEPDIR)/libisc_la-backtrace.Tpo -c -o libisc_la-backtrace.lo `test -f 'backtrace.c' || echo '$(srcdir)/'`backtrace.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-backtrace.Tpo $(DEPDIR)/libisc_la-backtrace.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='backtrace.c' object='libisc_la-backtrace.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-backtrace.lo `test -f 'backtrace.c' || echo '$(srcdir)/'`backtrace.c
+
+libisc_la-base32.lo: base32.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-base32.lo -MD -MP -MF $(DEPDIR)/libisc_la-base32.Tpo -c -o libisc_la-base32.lo `test -f 'base32.c' || echo '$(srcdir)/'`base32.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-base32.Tpo $(DEPDIR)/libisc_la-base32.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base32.c' object='libisc_la-base32.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-base32.lo `test -f 'base32.c' || echo '$(srcdir)/'`base32.c
+
+libisc_la-base64.lo: base64.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-base64.lo -MD -MP -MF $(DEPDIR)/libisc_la-base64.Tpo -c -o libisc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-base64.Tpo $(DEPDIR)/libisc_la-base64.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base64.c' object='libisc_la-base64.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+
+libisc_la-buffer.lo: buffer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-buffer.lo -MD -MP -MF $(DEPDIR)/libisc_la-buffer.Tpo -c -o libisc_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-buffer.Tpo $(DEPDIR)/libisc_la-buffer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='buffer.c' object='libisc_la-buffer.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-buffer.lo `test -f 'buffer.c' || echo '$(srcdir)/'`buffer.c
+
+libisc_la-commandline.lo: commandline.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-commandline.lo -MD -MP -MF $(DEPDIR)/libisc_la-commandline.Tpo -c -o libisc_la-commandline.lo `test -f 'commandline.c' || echo '$(srcdir)/'`commandline.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-commandline.Tpo $(DEPDIR)/libisc_la-commandline.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='commandline.c' object='libisc_la-commandline.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-commandline.lo `test -f 'commandline.c' || echo '$(srcdir)/'`commandline.c
+
+libisc_la-condition.lo: condition.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-condition.lo -MD -MP -MF $(DEPDIR)/libisc_la-condition.Tpo -c -o libisc_la-condition.lo `test -f 'condition.c' || echo '$(srcdir)/'`condition.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-condition.Tpo $(DEPDIR)/libisc_la-condition.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='condition.c' object='libisc_la-condition.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-condition.lo `test -f 'condition.c' || echo '$(srcdir)/'`condition.c
+
+libisc_la-counter.lo: counter.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-counter.lo -MD -MP -MF $(DEPDIR)/libisc_la-counter.Tpo -c -o libisc_la-counter.lo `test -f 'counter.c' || echo '$(srcdir)/'`counter.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-counter.Tpo $(DEPDIR)/libisc_la-counter.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='counter.c' object='libisc_la-counter.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-counter.lo `test -f 'counter.c' || echo '$(srcdir)/'`counter.c
+
+libisc_la-crc64.lo: crc64.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-crc64.lo -MD -MP -MF $(DEPDIR)/libisc_la-crc64.Tpo -c -o libisc_la-crc64.lo `test -f 'crc64.c' || echo '$(srcdir)/'`crc64.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-crc64.Tpo $(DEPDIR)/libisc_la-crc64.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='crc64.c' object='libisc_la-crc64.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-crc64.lo `test -f 'crc64.c' || echo '$(srcdir)/'`crc64.c
+
+libisc_la-dir.lo: dir.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-dir.lo -MD -MP -MF $(DEPDIR)/libisc_la-dir.Tpo -c -o libisc_la-dir.lo `test -f 'dir.c' || echo '$(srcdir)/'`dir.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-dir.Tpo $(DEPDIR)/libisc_la-dir.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dir.c' object='libisc_la-dir.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-dir.lo `test -f 'dir.c' || echo '$(srcdir)/'`dir.c
+
+libisc_la-entropy.lo: entropy.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-entropy.lo -MD -MP -MF $(DEPDIR)/libisc_la-entropy.Tpo -c -o libisc_la-entropy.lo `test -f 'entropy.c' || echo '$(srcdir)/'`entropy.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-entropy.Tpo $(DEPDIR)/libisc_la-entropy.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='entropy.c' object='libisc_la-entropy.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-entropy.lo `test -f 'entropy.c' || echo '$(srcdir)/'`entropy.c
+
+libisc_la-errno.lo: errno.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-errno.lo -MD -MP -MF $(DEPDIR)/libisc_la-errno.Tpo -c -o libisc_la-errno.lo `test -f 'errno.c' || echo '$(srcdir)/'`errno.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-errno.Tpo $(DEPDIR)/libisc_la-errno.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='errno.c' object='libisc_la-errno.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-errno.lo `test -f 'errno.c' || echo '$(srcdir)/'`errno.c
+
+libisc_la-errno2result.lo: errno2result.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-errno2result.lo -MD -MP -MF $(DEPDIR)/libisc_la-errno2result.Tpo -c -o libisc_la-errno2result.lo `test -f 'errno2result.c' || echo '$(srcdir)/'`errno2result.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-errno2result.Tpo $(DEPDIR)/libisc_la-errno2result.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='errno2result.c' object='libisc_la-errno2result.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-errno2result.lo `test -f 'errno2result.c' || echo '$(srcdir)/'`errno2result.c
+
+libisc_la-error.lo: error.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-error.lo -MD -MP -MF $(DEPDIR)/libisc_la-error.Tpo -c -o libisc_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-error.Tpo $(DEPDIR)/libisc_la-error.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='error.c' object='libisc_la-error.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-error.lo `test -f 'error.c' || echo '$(srcdir)/'`error.c
+
+libisc_la-event.lo: event.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-event.lo -MD -MP -MF $(DEPDIR)/libisc_la-event.Tpo -c -o libisc_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-event.Tpo $(DEPDIR)/libisc_la-event.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='event.c' object='libisc_la-event.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-event.lo `test -f 'event.c' || echo '$(srcdir)/'`event.c
+
+libisc_la-file.lo: file.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-file.lo -MD -MP -MF $(DEPDIR)/libisc_la-file.Tpo -c -o libisc_la-file.lo `test -f 'file.c' || echo '$(srcdir)/'`file.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-file.Tpo $(DEPDIR)/libisc_la-file.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='file.c' object='libisc_la-file.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-file.lo `test -f 'file.c' || echo '$(srcdir)/'`file.c
+
+libisc_la-glob.lo: glob.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-glob.lo -MD -MP -MF $(DEPDIR)/libisc_la-glob.Tpo -c -o libisc_la-glob.lo `test -f 'glob.c' || echo '$(srcdir)/'`glob.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-glob.Tpo $(DEPDIR)/libisc_la-glob.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='glob.c' object='libisc_la-glob.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-glob.lo `test -f 'glob.c' || echo '$(srcdir)/'`glob.c
+
+libisc_la-hash.lo: hash.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hash.lo -MD -MP -MF $(DEPDIR)/libisc_la-hash.Tpo -c -o libisc_la-hash.lo `test -f 'hash.c' || echo '$(srcdir)/'`hash.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hash.Tpo $(DEPDIR)/libisc_la-hash.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hash.c' object='libisc_la-hash.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hash.lo `test -f 'hash.c' || echo '$(srcdir)/'`hash.c
+
+libisc_la-heap.lo: heap.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-heap.lo -MD -MP -MF $(DEPDIR)/libisc_la-heap.Tpo -c -o libisc_la-heap.lo `test -f 'heap.c' || echo '$(srcdir)/'`heap.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-heap.Tpo $(DEPDIR)/libisc_la-heap.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='heap.c' object='libisc_la-heap.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-heap.lo `test -f 'heap.c' || echo '$(srcdir)/'`heap.c
+
+libisc_la-hex.lo: hex.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hex.lo -MD -MP -MF $(DEPDIR)/libisc_la-hex.Tpo -c -o libisc_la-hex.lo `test -f 'hex.c' || echo '$(srcdir)/'`hex.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hex.Tpo $(DEPDIR)/libisc_la-hex.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hex.c' object='libisc_la-hex.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hex.lo `test -f 'hex.c' || echo '$(srcdir)/'`hex.c
+
+libisc_la-hmac.lo: hmac.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-hmac.lo -MD -MP -MF $(DEPDIR)/libisc_la-hmac.Tpo -c -o libisc_la-hmac.lo `test -f 'hmac.c' || echo '$(srcdir)/'`hmac.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-hmac.Tpo $(DEPDIR)/libisc_la-hmac.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hmac.c' object='libisc_la-hmac.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-hmac.lo `test -f 'hmac.c' || echo '$(srcdir)/'`hmac.c
+
+libisc_la-ht.lo: ht.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-ht.lo -MD -MP -MF $(DEPDIR)/libisc_la-ht.Tpo -c -o libisc_la-ht.lo `test -f 'ht.c' || echo '$(srcdir)/'`ht.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-ht.Tpo $(DEPDIR)/libisc_la-ht.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ht.c' object='libisc_la-ht.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-ht.lo `test -f 'ht.c' || echo '$(srcdir)/'`ht.c
+
+libisc_la-httpd.lo: httpd.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-httpd.lo -MD -MP -MF $(DEPDIR)/libisc_la-httpd.Tpo -c -o libisc_la-httpd.lo `test -f 'httpd.c' || echo '$(srcdir)/'`httpd.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-httpd.Tpo $(DEPDIR)/libisc_la-httpd.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='httpd.c' object='libisc_la-httpd.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-httpd.lo `test -f 'httpd.c' || echo '$(srcdir)/'`httpd.c
+
+libisc_la-interfaceiter.lo: interfaceiter.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-interfaceiter.lo -MD -MP -MF $(DEPDIR)/libisc_la-interfaceiter.Tpo -c -o libisc_la-interfaceiter.lo `test -f 'interfaceiter.c' || echo '$(srcdir)/'`interfaceiter.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-interfaceiter.Tpo $(DEPDIR)/libisc_la-interfaceiter.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfaceiter.c' object='libisc_la-interfaceiter.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-interfaceiter.lo `test -f 'interfaceiter.c' || echo '$(srcdir)/'`interfaceiter.c
+
+libisc_la-iterated_hash.lo: iterated_hash.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-iterated_hash.lo -MD -MP -MF $(DEPDIR)/libisc_la-iterated_hash.Tpo -c -o libisc_la-iterated_hash.lo `test -f 'iterated_hash.c' || echo '$(srcdir)/'`iterated_hash.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-iterated_hash.Tpo $(DEPDIR)/libisc_la-iterated_hash.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='iterated_hash.c' object='libisc_la-iterated_hash.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-iterated_hash.lo `test -f 'iterated_hash.c' || echo '$(srcdir)/'`iterated_hash.c
+
+libisc_la-lex.lo: lex.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-lex.lo -MD -MP -MF $(DEPDIR)/libisc_la-lex.Tpo -c -o libisc_la-lex.lo `test -f 'lex.c' || echo '$(srcdir)/'`lex.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-lex.Tpo $(DEPDIR)/libisc_la-lex.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lex.c' object='libisc_la-lex.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-lex.lo `test -f 'lex.c' || echo '$(srcdir)/'`lex.c
+
+libisc_la-lib.lo: lib.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-lib.lo -MD -MP -MF $(DEPDIR)/libisc_la-lib.Tpo -c -o libisc_la-lib.lo `test -f 'lib.c' || echo '$(srcdir)/'`lib.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-lib.Tpo $(DEPDIR)/libisc_la-lib.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib.c' object='libisc_la-lib.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-lib.lo `test -f 'lib.c' || echo '$(srcdir)/'`lib.c
+
+libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-log.lo -MD -MP -MF $(DEPDIR)/libisc_la-log.Tpo -c -o libisc_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-log.Tpo $(DEPDIR)/libisc_la-log.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+libisc_la-managers.lo: managers.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-managers.lo -MD -MP -MF $(DEPDIR)/libisc_la-managers.Tpo -c -o libisc_la-managers.lo `test -f 'managers.c' || echo '$(srcdir)/'`managers.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-managers.Tpo $(DEPDIR)/libisc_la-managers.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='managers.c' object='libisc_la-managers.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-managers.lo `test -f 'managers.c' || echo '$(srcdir)/'`managers.c
+
+libisc_la-md.lo: md.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-md.lo -MD -MP -MF $(DEPDIR)/libisc_la-md.Tpo -c -o libisc_la-md.lo `test -f 'md.c' || echo '$(srcdir)/'`md.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-md.Tpo $(DEPDIR)/libisc_la-md.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='md.c' object='libisc_la-md.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-md.lo `test -f 'md.c' || echo '$(srcdir)/'`md.c
+
+libisc_la-mem.lo: mem.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mem.lo -MD -MP -MF $(DEPDIR)/libisc_la-mem.Tpo -c -o libisc_la-mem.lo `test -f 'mem.c' || echo '$(srcdir)/'`mem.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mem.Tpo $(DEPDIR)/libisc_la-mem.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mem.c' object='libisc_la-mem.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mem.lo `test -f 'mem.c' || echo '$(srcdir)/'`mem.c
+
+libisc_la-meminfo.lo: meminfo.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-meminfo.lo -MD -MP -MF $(DEPDIR)/libisc_la-meminfo.Tpo -c -o libisc_la-meminfo.lo `test -f 'meminfo.c' || echo '$(srcdir)/'`meminfo.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-meminfo.Tpo $(DEPDIR)/libisc_la-meminfo.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='meminfo.c' object='libisc_la-meminfo.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-meminfo.lo `test -f 'meminfo.c' || echo '$(srcdir)/'`meminfo.c
+
+libisc_la-mutex.lo: mutex.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mutex.lo -MD -MP -MF $(DEPDIR)/libisc_la-mutex.Tpo -c -o libisc_la-mutex.lo `test -f 'mutex.c' || echo '$(srcdir)/'`mutex.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mutex.Tpo $(DEPDIR)/libisc_la-mutex.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mutex.c' object='libisc_la-mutex.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mutex.lo `test -f 'mutex.c' || echo '$(srcdir)/'`mutex.c
+
+libisc_la-mutexblock.lo: mutexblock.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-mutexblock.lo -MD -MP -MF $(DEPDIR)/libisc_la-mutexblock.Tpo -c -o libisc_la-mutexblock.lo `test -f 'mutexblock.c' || echo '$(srcdir)/'`mutexblock.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-mutexblock.Tpo $(DEPDIR)/libisc_la-mutexblock.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='mutexblock.c' object='libisc_la-mutexblock.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-mutexblock.lo `test -f 'mutexblock.c' || echo '$(srcdir)/'`mutexblock.c
+
+libisc_la-net.lo: net.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-net.lo -MD -MP -MF $(DEPDIR)/libisc_la-net.Tpo -c -o libisc_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-net.Tpo $(DEPDIR)/libisc_la-net.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='net.c' object='libisc_la-net.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c
+
+libisc_la-netaddr.lo: netaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-netaddr.lo -MD -MP -MF $(DEPDIR)/libisc_la-netaddr.Tpo -c -o libisc_la-netaddr.lo `test -f 'netaddr.c' || echo '$(srcdir)/'`netaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-netaddr.Tpo $(DEPDIR)/libisc_la-netaddr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netaddr.c' object='libisc_la-netaddr.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-netaddr.lo `test -f 'netaddr.c' || echo '$(srcdir)/'`netaddr.c
+
+libisc_la-netscope.lo: netscope.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-netscope.lo -MD -MP -MF $(DEPDIR)/libisc_la-netscope.Tpo -c -o libisc_la-netscope.lo `test -f 'netscope.c' || echo '$(srcdir)/'`netscope.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-netscope.Tpo $(DEPDIR)/libisc_la-netscope.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netscope.c' object='libisc_la-netscope.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-netscope.lo `test -f 'netscope.c' || echo '$(srcdir)/'`netscope.c
+
+libisc_la-nonce.lo: nonce.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-nonce.lo -MD -MP -MF $(DEPDIR)/libisc_la-nonce.Tpo -c -o libisc_la-nonce.lo `test -f 'nonce.c' || echo '$(srcdir)/'`nonce.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-nonce.Tpo $(DEPDIR)/libisc_la-nonce.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nonce.c' object='libisc_la-nonce.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-nonce.lo `test -f 'nonce.c' || echo '$(srcdir)/'`nonce.c
+
+libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-openssl_shim.lo -MD -MP -MF $(DEPDIR)/libisc_la-openssl_shim.Tpo -c -o libisc_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-openssl_shim.Tpo $(DEPDIR)/libisc_la-openssl_shim.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='openssl_shim.c' object='libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-openssl_shim.lo `test -f 'openssl_shim.c' || echo '$(srcdir)/'`openssl_shim.c
+
+libisc_la-os.lo: os.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-os.lo -MD -MP -MF $(DEPDIR)/libisc_la-os.Tpo -c -o libisc_la-os.lo `test -f 'os.c' || echo '$(srcdir)/'`os.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-os.Tpo $(DEPDIR)/libisc_la-os.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='os.c' object='libisc_la-os.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-os.lo `test -f 'os.c' || echo '$(srcdir)/'`os.c
+
+libisc_la-parseint.lo: parseint.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-parseint.lo -MD -MP -MF $(DEPDIR)/libisc_la-parseint.Tpo -c -o libisc_la-parseint.lo `test -f 'parseint.c' || echo '$(srcdir)/'`parseint.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-parseint.Tpo $(DEPDIR)/libisc_la-parseint.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='parseint.c' object='libisc_la-parseint.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-parseint.lo `test -f 'parseint.c' || echo '$(srcdir)/'`parseint.c
+
+libisc_la-pool.lo: pool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-pool.lo -MD -MP -MF $(DEPDIR)/libisc_la-pool.Tpo -c -o libisc_la-pool.lo `test -f 'pool.c' || echo '$(srcdir)/'`pool.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-pool.Tpo $(DEPDIR)/libisc_la-pool.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='pool.c' object='libisc_la-pool.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-pool.lo `test -f 'pool.c' || echo '$(srcdir)/'`pool.c
+
+libisc_la-picohttpparser.lo: picohttpparser.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-picohttpparser.lo -MD -MP -MF $(DEPDIR)/libisc_la-picohttpparser.Tpo -c -o libisc_la-picohttpparser.lo `test -f 'picohttpparser.c' || echo '$(srcdir)/'`picohttpparser.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-picohttpparser.Tpo $(DEPDIR)/libisc_la-picohttpparser.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='picohttpparser.c' object='libisc_la-picohttpparser.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-picohttpparser.lo `test -f 'picohttpparser.c' || echo '$(srcdir)/'`picohttpparser.c
+
+libisc_la-portset.lo: portset.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-portset.lo -MD -MP -MF $(DEPDIR)/libisc_la-portset.Tpo -c -o libisc_la-portset.lo `test -f 'portset.c' || echo '$(srcdir)/'`portset.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-portset.Tpo $(DEPDIR)/libisc_la-portset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='portset.c' object='libisc_la-portset.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-portset.lo `test -f 'portset.c' || echo '$(srcdir)/'`portset.c
+
+libisc_la-quota.lo: quota.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-quota.lo -MD -MP -MF $(DEPDIR)/libisc_la-quota.Tpo -c -o libisc_la-quota.lo `test -f 'quota.c' || echo '$(srcdir)/'`quota.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-quota.Tpo $(DEPDIR)/libisc_la-quota.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='quota.c' object='libisc_la-quota.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-quota.lo `test -f 'quota.c' || echo '$(srcdir)/'`quota.c
+
+libisc_la-radix.lo: radix.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-radix.lo -MD -MP -MF $(DEPDIR)/libisc_la-radix.Tpo -c -o libisc_la-radix.lo `test -f 'radix.c' || echo '$(srcdir)/'`radix.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-radix.Tpo $(DEPDIR)/libisc_la-radix.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='radix.c' object='libisc_la-radix.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-radix.lo `test -f 'radix.c' || echo '$(srcdir)/'`radix.c
+
+libisc_la-random.lo: random.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-random.lo -MD -MP -MF $(DEPDIR)/libisc_la-random.Tpo -c -o libisc_la-random.lo `test -f 'random.c' || echo '$(srcdir)/'`random.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-random.Tpo $(DEPDIR)/libisc_la-random.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='random.c' object='libisc_la-random.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-random.lo `test -f 'random.c' || echo '$(srcdir)/'`random.c
+
+libisc_la-ratelimiter.lo: ratelimiter.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-ratelimiter.lo -MD -MP -MF $(DEPDIR)/libisc_la-ratelimiter.Tpo -c -o libisc_la-ratelimiter.lo `test -f 'ratelimiter.c' || echo '$(srcdir)/'`ratelimiter.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-ratelimiter.Tpo $(DEPDIR)/libisc_la-ratelimiter.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ratelimiter.c' object='libisc_la-ratelimiter.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-ratelimiter.lo `test -f 'ratelimiter.c' || echo '$(srcdir)/'`ratelimiter.c
+
+libisc_la-regex.lo: regex.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-regex.lo -MD -MP -MF $(DEPDIR)/libisc_la-regex.Tpo -c -o libisc_la-regex.lo `test -f 'regex.c' || echo '$(srcdir)/'`regex.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-regex.Tpo $(DEPDIR)/libisc_la-regex.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='regex.c' object='libisc_la-regex.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-regex.lo `test -f 'regex.c' || echo '$(srcdir)/'`regex.c
+
+libisc_la-region.lo: region.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-region.lo -MD -MP -MF $(DEPDIR)/libisc_la-region.Tpo -c -o libisc_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-region.Tpo $(DEPDIR)/libisc_la-region.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='region.c' object='libisc_la-region.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-region.lo `test -f 'region.c' || echo '$(srcdir)/'`region.c
+
+libisc_la-resource.lo: resource.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-resource.lo -MD -MP -MF $(DEPDIR)/libisc_la-resource.Tpo -c -o libisc_la-resource.lo `test -f 'resource.c' || echo '$(srcdir)/'`resource.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-resource.Tpo $(DEPDIR)/libisc_la-resource.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='resource.c' object='libisc_la-resource.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-resource.lo `test -f 'resource.c' || echo '$(srcdir)/'`resource.c
+
+libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-result.lo -MD -MP -MF $(DEPDIR)/libisc_la-result.Tpo -c -o libisc_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-result.Tpo $(DEPDIR)/libisc_la-result.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='result.c' object='libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-result.lo `test -f 'result.c' || echo '$(srcdir)/'`result.c
+
+libisc_la-rwlock.lo: rwlock.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-rwlock.lo -MD -MP -MF $(DEPDIR)/libisc_la-rwlock.Tpo -c -o libisc_la-rwlock.lo `test -f 'rwlock.c' || echo '$(srcdir)/'`rwlock.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-rwlock.Tpo $(DEPDIR)/libisc_la-rwlock.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rwlock.c' object='libisc_la-rwlock.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-rwlock.lo `test -f 'rwlock.c' || echo '$(srcdir)/'`rwlock.c
+
+libisc_la-safe.lo: safe.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-safe.lo -MD -MP -MF $(DEPDIR)/libisc_la-safe.Tpo -c -o libisc_la-safe.lo `test -f 'safe.c' || echo '$(srcdir)/'`safe.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-safe.Tpo $(DEPDIR)/libisc_la-safe.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='safe.c' object='libisc_la-safe.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-safe.lo `test -f 'safe.c' || echo '$(srcdir)/'`safe.c
+
+libisc_la-serial.lo: serial.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-serial.lo -MD -MP -MF $(DEPDIR)/libisc_la-serial.Tpo -c -o libisc_la-serial.lo `test -f 'serial.c' || echo '$(srcdir)/'`serial.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-serial.Tpo $(DEPDIR)/libisc_la-serial.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='serial.c' object='libisc_la-serial.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-serial.lo `test -f 'serial.c' || echo '$(srcdir)/'`serial.c
+
+libisc_la-siphash.lo: siphash.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-siphash.lo -MD -MP -MF $(DEPDIR)/libisc_la-siphash.Tpo -c -o libisc_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-siphash.Tpo $(DEPDIR)/libisc_la-siphash.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='siphash.c' object='libisc_la-siphash.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-siphash.lo `test -f 'siphash.c' || echo '$(srcdir)/'`siphash.c
+
+libisc_la-sockaddr.lo: sockaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-sockaddr.lo -MD -MP -MF $(DEPDIR)/libisc_la-sockaddr.Tpo -c -o libisc_la-sockaddr.lo `test -f 'sockaddr.c' || echo '$(srcdir)/'`sockaddr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-sockaddr.Tpo $(DEPDIR)/libisc_la-sockaddr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sockaddr.c' object='libisc_la-sockaddr.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-sockaddr.lo `test -f 'sockaddr.c' || echo '$(srcdir)/'`sockaddr.c
+
+libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stats.lo -MD -MP -MF $(DEPDIR)/libisc_la-stats.Tpo -c -o libisc_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stats.Tpo $(DEPDIR)/libisc_la-stats.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stats.c' object='libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+
+libisc_la-stdio.lo: stdio.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stdio.lo -MD -MP -MF $(DEPDIR)/libisc_la-stdio.Tpo -c -o libisc_la-stdio.lo `test -f 'stdio.c' || echo '$(srcdir)/'`stdio.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stdio.Tpo $(DEPDIR)/libisc_la-stdio.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stdio.c' object='libisc_la-stdio.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stdio.lo `test -f 'stdio.c' || echo '$(srcdir)/'`stdio.c
+
+libisc_la-stdtime.lo: stdtime.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-stdtime.lo -MD -MP -MF $(DEPDIR)/libisc_la-stdtime.Tpo -c -o libisc_la-stdtime.lo `test -f 'stdtime.c' || echo '$(srcdir)/'`stdtime.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-stdtime.Tpo $(DEPDIR)/libisc_la-stdtime.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stdtime.c' object='libisc_la-stdtime.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-stdtime.lo `test -f 'stdtime.c' || echo '$(srcdir)/'`stdtime.c
+
+libisc_la-string.lo: string.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-string.lo -MD -MP -MF $(DEPDIR)/libisc_la-string.Tpo -c -o libisc_la-string.lo `test -f 'string.c' || echo '$(srcdir)/'`string.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-string.Tpo $(DEPDIR)/libisc_la-string.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='string.c' object='libisc_la-string.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-string.lo `test -f 'string.c' || echo '$(srcdir)/'`string.c
+
+libisc_la-symtab.lo: symtab.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-symtab.lo -MD -MP -MF $(DEPDIR)/libisc_la-symtab.Tpo -c -o libisc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-symtab.Tpo $(DEPDIR)/libisc_la-symtab.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='symtab.c' object='libisc_la-symtab.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c
+
+libisc_la-syslog.lo: syslog.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-syslog.lo -MD -MP -MF $(DEPDIR)/libisc_la-syslog.Tpo -c -o libisc_la-syslog.lo `test -f 'syslog.c' || echo '$(srcdir)/'`syslog.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-syslog.Tpo $(DEPDIR)/libisc_la-syslog.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='syslog.c' object='libisc_la-syslog.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-syslog.lo `test -f 'syslog.c' || echo '$(srcdir)/'`syslog.c
+
+libisc_la-task.lo: task.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-task.lo -MD -MP -MF $(DEPDIR)/libisc_la-task.Tpo -c -o libisc_la-task.lo `test -f 'task.c' || echo '$(srcdir)/'`task.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-task.Tpo $(DEPDIR)/libisc_la-task.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='task.c' object='libisc_la-task.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-task.lo `test -f 'task.c' || echo '$(srcdir)/'`task.c
+
+libisc_la-taskpool.lo: taskpool.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-taskpool.lo -MD -MP -MF $(DEPDIR)/libisc_la-taskpool.Tpo -c -o libisc_la-taskpool.lo `test -f 'taskpool.c' || echo '$(srcdir)/'`taskpool.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-taskpool.Tpo $(DEPDIR)/libisc_la-taskpool.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='taskpool.c' object='libisc_la-taskpool.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-taskpool.lo `test -f 'taskpool.c' || echo '$(srcdir)/'`taskpool.c
+
+libisc_la-thread.lo: thread.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-thread.lo -MD -MP -MF $(DEPDIR)/libisc_la-thread.Tpo -c -o libisc_la-thread.lo `test -f 'thread.c' || echo '$(srcdir)/'`thread.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-thread.Tpo $(DEPDIR)/libisc_la-thread.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='thread.c' object='libisc_la-thread.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-thread.lo `test -f 'thread.c' || echo '$(srcdir)/'`thread.c
+
+libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-time.lo -MD -MP -MF $(DEPDIR)/libisc_la-time.Tpo -c -o libisc_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-time.Tpo $(DEPDIR)/libisc_la-time.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='time.c' object='libisc_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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-time.lo `test -f 'time.c' || echo '$(srcdir)/'`time.c
+
+libisc_la-timer.lo: timer.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-timer.lo -MD -MP -MF $(DEPDIR)/libisc_la-timer.Tpo -c -o libisc_la-timer.lo `test -f 'timer.c' || echo '$(srcdir)/'`timer.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-timer.Tpo $(DEPDIR)/libisc_la-timer.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='timer.c' object='libisc_la-timer.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-timer.lo `test -f 'timer.c' || echo '$(srcdir)/'`timer.c
+
+libisc_la-tls.lo: tls.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-tls.lo -MD -MP -MF $(DEPDIR)/libisc_la-tls.Tpo -c -o libisc_la-tls.lo `test -f 'tls.c' || echo '$(srcdir)/'`tls.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-tls.Tpo $(DEPDIR)/libisc_la-tls.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tls.c' object='libisc_la-tls.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-tls.lo `test -f 'tls.c' || echo '$(srcdir)/'`tls.c
+
+libisc_la-tm.lo: tm.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-tm.lo -MD -MP -MF $(DEPDIR)/libisc_la-tm.Tpo -c -o libisc_la-tm.lo `test -f 'tm.c' || echo '$(srcdir)/'`tm.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-tm.Tpo $(DEPDIR)/libisc_la-tm.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tm.c' object='libisc_la-tm.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-tm.lo `test -f 'tm.c' || echo '$(srcdir)/'`tm.c
+
+libisc_la-trampoline.lo: trampoline.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-trampoline.lo -MD -MP -MF $(DEPDIR)/libisc_la-trampoline.Tpo -c -o libisc_la-trampoline.lo `test -f 'trampoline.c' || echo '$(srcdir)/'`trampoline.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-trampoline.Tpo $(DEPDIR)/libisc_la-trampoline.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='trampoline.c' object='libisc_la-trampoline.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-trampoline.lo `test -f 'trampoline.c' || echo '$(srcdir)/'`trampoline.c
+
+libisc_la-url.lo: url.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-url.lo -MD -MP -MF $(DEPDIR)/libisc_la-url.Tpo -c -o libisc_la-url.lo `test -f 'url.c' || echo '$(srcdir)/'`url.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-url.Tpo $(DEPDIR)/libisc_la-url.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='url.c' object='libisc_la-url.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-url.lo `test -f 'url.c' || echo '$(srcdir)/'`url.c
+
+libisc_la-utf8.lo: utf8.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisc_la-utf8.lo -MD -MP -MF $(DEPDIR)/libisc_la-utf8.Tpo -c -o libisc_la-utf8.lo `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisc_la-utf8.Tpo $(DEPDIR)/libisc_la-utf8.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='utf8.c' object='libisc_la-utf8.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisc_la-utf8.lo `test -f 'utf8.c' || echo '$(srcdir)/'`utf8.c
+
+netmgr/libisc_la-http.lo: netmgr/http.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-http.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-http.Tpo -c -o netmgr/libisc_la-http.lo `test -f 'netmgr/http.c' || echo '$(srcdir)/'`netmgr/http.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-http.Tpo netmgr/$(DEPDIR)/libisc_la-http.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/http.c' object='netmgr/libisc_la-http.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-http.lo `test -f 'netmgr/http.c' || echo '$(srcdir)/'`netmgr/http.c
+
+netmgr/libisc_la-tlsstream.lo: netmgr/tlsstream.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT netmgr/libisc_la-tlsstream.lo -MD -MP -MF netmgr/$(DEPDIR)/libisc_la-tlsstream.Tpo -c -o netmgr/libisc_la-tlsstream.lo `test -f 'netmgr/tlsstream.c' || echo '$(srcdir)/'`netmgr/tlsstream.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) netmgr/$(DEPDIR)/libisc_la-tlsstream.Tpo netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netmgr/tlsstream.c' object='netmgr/libisc_la-tlsstream.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) $(libisc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o netmgr/libisc_la-tlsstream.lo `test -f 'netmgr/tlsstream.c' || echo '$(srcdir)/'`netmgr/tlsstream.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf netmgr/.libs netmgr/_libs
+install-libisc_laHEADERS: $(libisc_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libisc_la_HEADERS)'; test -n "$(libisc_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libisc_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libisc_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)$(libisc_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libisc_ladir)" || exit $$?; \
+ done
+
+uninstall-libisc_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libisc_la_HEADERS)'; test -n "$(libisc_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libisc_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisc_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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)
+ -rm -f netmgr/$(DEPDIR)/$(am__dirstamp)
+ -rm -f netmgr/$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libisc_la-aes.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-app.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-assertions.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-astack.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-backtrace.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-base32.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-base64.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-buffer.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-commandline.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-condition.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-counter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-crc64.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-dir.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-entropy.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-errno.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-errno2result.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-error.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-event.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-file.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-glob.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-heap.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hmac.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-ht.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-httpd.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-interfaceiter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-iterated_hash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-lex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-lib.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-managers.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-md.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mem.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-meminfo.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mutex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mutexblock.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-net.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-netaddr.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-netscope.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-nonce.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-openssl_shim.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-os.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-parseint.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-picohttpparser.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-pool.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-portset.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-quota.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-radix.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-random.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-ratelimiter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-regex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-region.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-result.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-rwlock.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-safe.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-serial.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-siphash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-sockaddr.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stdio.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stdtime.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-string.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-symtab.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-syslog.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-task.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-taskpool.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-thread.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-time.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-timer.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-tls.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-tm.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-trampoline.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-url.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-utf8.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-http.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-netmgr.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tcp.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-timer.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-udp.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-uverr2result.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-libisc_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)/libisc_la-aes.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-app.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-assertions.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-astack.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-backtrace.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-base32.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-base64.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-buffer.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-commandline.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-condition.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-counter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-crc64.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-dir.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-entropy.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-errno.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-errno2result.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-error.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-event.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-file.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-glob.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-heap.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-hmac.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-ht.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-httpd.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-interfaceiter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-iterated_hash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-lex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-lib.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-managers.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-md.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mem.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-meminfo.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mutex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-mutexblock.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-net.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-netaddr.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-netscope.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-nonce.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-openssl_shim.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-os.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-parseint.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-picohttpparser.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-pool.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-portset.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-quota.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-radix.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-random.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-ratelimiter.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-regex.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-region.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-resource.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-result.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-rwlock.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-safe.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-serial.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-siphash.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-sockaddr.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stdio.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-stdtime.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-string.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-symtab.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-syslog.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-task.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-taskpool.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-thread.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-time.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-timer.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-tls.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-tm.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-trampoline.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-url.Plo
+ -rm -f ./$(DEPDIR)/libisc_la-utf8.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-http.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-netmgr.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tcp.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tcpdns.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-timer.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tlsdns.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-tlsstream.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-udp.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-uv-compat.Plo
+ -rm -f netmgr/$(DEPDIR)/libisc_la-uverr2result.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-libLTLIBRARIES uninstall-libisc_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libisc_laHEADERS install-man 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-libLTLIBRARIES uninstall-libisc_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/isc/aes.c b/lib/isc/aes.c
new file mode 100644
index 0000000..d136bd4
--- /dev/null
+++ b/lib/isc/aes.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 isc/aes.c */
+
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+
+#include <isc/aes.h>
+#include <isc/assertions.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+void
+isc_aes128_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out) {
+ EVP_CIPHER_CTX *c;
+ int len;
+
+ c = EVP_CIPHER_CTX_new();
+ RUNTIME_CHECK(c != NULL);
+ RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_128_ecb(), key, NULL) == 1);
+ EVP_CIPHER_CTX_set_padding(c, 0);
+ RUNTIME_CHECK(
+ EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1);
+ RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH);
+ EVP_CIPHER_CTX_free(c);
+}
+
+void
+isc_aes192_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out) {
+ EVP_CIPHER_CTX *c;
+ int len;
+
+ c = EVP_CIPHER_CTX_new();
+ RUNTIME_CHECK(c != NULL);
+ RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_192_ecb(), key, NULL) == 1);
+ EVP_CIPHER_CTX_set_padding(c, 0);
+ RUNTIME_CHECK(
+ EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1);
+ RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH);
+ EVP_CIPHER_CTX_free(c);
+}
+
+void
+isc_aes256_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out) {
+ EVP_CIPHER_CTX *c;
+ int len;
+
+ c = EVP_CIPHER_CTX_new();
+ RUNTIME_CHECK(c != NULL);
+ RUNTIME_CHECK(EVP_EncryptInit(c, EVP_aes_256_ecb(), key, NULL) == 1);
+ EVP_CIPHER_CTX_set_padding(c, 0);
+ RUNTIME_CHECK(
+ EVP_EncryptUpdate(c, out, &len, in, ISC_AES_BLOCK_LENGTH) == 1);
+ RUNTIME_CHECK(len == ISC_AES_BLOCK_LENGTH);
+ EVP_CIPHER_CTX_free(c);
+}
diff --git a/lib/isc/app.c b/lib/isc/app.c
new file mode 100644
index 0000000..da438d7
--- /dev/null
+++ b/lib/isc/app.c
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/atomic.h>
+#include <isc/condition.h>
+#include <isc/event.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+/*%
+ * For BIND9 applications built with threads, we use a single app
+ * context and let multiple taskmgr and netmgr threads do actual jobs.
+ */
+
+static isc_thread_t blockedthread;
+static atomic_bool is_running = 0;
+
+/*
+ * The application context of this module.
+ */
+#define APPCTX_MAGIC ISC_MAGIC('A', 'p', 'c', 'x')
+#define VALID_APPCTX(c) ISC_MAGIC_VALID(c, APPCTX_MAGIC)
+
+struct isc_appctx {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_eventlist_t on_run;
+ atomic_bool shutdown_requested;
+ atomic_bool running;
+ atomic_bool want_shutdown;
+ atomic_bool want_reload;
+ atomic_bool blocked;
+ isc_mutex_t readylock;
+ isc_condition_t ready;
+};
+
+static isc_appctx_t isc_g_appctx;
+
+static void
+handle_signal(int sig, void (*handler)(int)) {
+ struct sigaction sa;
+
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = handler;
+
+ if (sigfillset(&sa.sa_mask) != 0 || sigaction(sig, &sa, NULL) < 0) {
+ FATAL_SYSERROR(errno, "signal %d", sig);
+ }
+}
+
+isc_result_t
+isc_app_ctxstart(isc_appctx_t *ctx) {
+ int presult;
+ sigset_t sset;
+
+ REQUIRE(VALID_APPCTX(ctx));
+
+ /*
+ * Start an ISC library application.
+ */
+
+ isc_mutex_init(&ctx->lock);
+
+ isc_mutex_init(&ctx->readylock);
+ isc_condition_init(&ctx->ready);
+
+ ISC_LIST_INIT(ctx->on_run);
+
+ atomic_init(&ctx->shutdown_requested, false);
+ atomic_init(&ctx->running, false);
+ atomic_init(&ctx->want_shutdown, false);
+ atomic_init(&ctx->want_reload, false);
+ atomic_init(&ctx->blocked, false);
+
+ /*
+ * Always ignore SIGPIPE.
+ */
+ handle_signal(SIGPIPE, SIG_IGN);
+
+ handle_signal(SIGHUP, SIG_DFL);
+ handle_signal(SIGTERM, SIG_DFL);
+ handle_signal(SIGINT, SIG_DFL);
+
+ /*
+ * Block SIGHUP, SIGINT, SIGTERM.
+ *
+ * If isc_app_start() is called from the main thread before any other
+ * threads have been created, then the pthread_sigmask() call below
+ * will result in all threads having SIGHUP, SIGINT and SIGTERM
+ * blocked by default, ensuring that only the thread that calls
+ * sigwait() for them will get those signals.
+ */
+ if (sigemptyset(&sset) != 0 || sigaddset(&sset, SIGHUP) != 0 ||
+ sigaddset(&sset, SIGINT) != 0 || sigaddset(&sset, SIGTERM) != 0)
+ {
+ FATAL_SYSERROR(errno, "sigsetops");
+ }
+ presult = pthread_sigmask(SIG_BLOCK, &sset, NULL);
+ if (presult != 0) {
+ FATAL_SYSERROR(presult, "pthread_sigmask()");
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_app_start(void) {
+ isc_g_appctx.magic = APPCTX_MAGIC;
+ isc_g_appctx.mctx = NULL;
+ /* The remaining members will be initialized in ctxstart() */
+
+ return (isc_app_ctxstart(&isc_g_appctx));
+}
+
+isc_result_t
+isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action,
+ void *arg) {
+ return (isc_app_ctxonrun(&isc_g_appctx, mctx, task, action, arg));
+}
+
+isc_result_t
+isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task,
+ isc_taskaction_t action, void *arg) {
+ isc_event_t *event;
+ isc_task_t *cloned_task = NULL;
+
+ if (atomic_load_acquire(&ctx->running)) {
+ return (ISC_R_ALREADYRUNNING);
+ }
+
+ /*
+ * Note that we store the task to which we're going to send the event
+ * in the event's "sender" field.
+ */
+ isc_task_attach(task, &cloned_task);
+ event = isc_event_allocate(mctx, cloned_task, ISC_APPEVENT_SHUTDOWN,
+ action, arg, sizeof(*event));
+
+ LOCK(&ctx->lock);
+ ISC_LINK_INIT(event, ev_link);
+ ISC_LIST_APPEND(ctx->on_run, event, ev_link);
+ UNLOCK(&ctx->lock);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_app_ctxrun(isc_appctx_t *ctx) {
+ isc_event_t *event, *next_event;
+ isc_task_t *task;
+
+ REQUIRE(VALID_APPCTX(ctx));
+
+ if (atomic_compare_exchange_strong_acq_rel(&ctx->running,
+ &(bool){ false }, true))
+ {
+ /*
+ * Post any on-run events (in FIFO order).
+ */
+ LOCK(&ctx->lock);
+ for (event = ISC_LIST_HEAD(ctx->on_run); event != NULL;
+ event = next_event)
+ {
+ next_event = ISC_LIST_NEXT(event, ev_link);
+ ISC_LIST_UNLINK(ctx->on_run, event, ev_link);
+ task = event->ev_sender;
+ event->ev_sender = NULL;
+ isc_task_sendanddetach(&task, &event);
+ }
+ UNLOCK(&ctx->lock);
+ }
+
+ /*
+ * There is no danger if isc_app_shutdown() is called before we
+ * wait for signals. Signals are blocked, so any such signal will
+ * simply be made pending and we will get it when we call
+ * sigwait().
+ */
+ while (!atomic_load_acquire(&ctx->want_shutdown)) {
+ if (ctx == &isc_g_appctx) {
+ sigset_t sset;
+ int sig;
+ /*
+ * Wait for SIGHUP, SIGINT, or SIGTERM.
+ */
+ if (sigemptyset(&sset) != 0 ||
+ sigaddset(&sset, SIGHUP) != 0 ||
+ sigaddset(&sset, SIGINT) != 0 ||
+ sigaddset(&sset, SIGTERM) != 0)
+ {
+ FATAL_SYSERROR(errno, "sigsetops");
+ }
+
+ if (sigwait(&sset, &sig) == 0) {
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ atomic_store_release(
+ &ctx->want_shutdown, true);
+ break;
+ case SIGHUP:
+ atomic_store_release(&ctx->want_reload,
+ true);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ } else {
+ /*
+ * Tools using multiple contexts don't
+ * rely on a signal, just wait until woken
+ * up.
+ */
+ if (atomic_load_acquire(&ctx->want_shutdown)) {
+ break;
+ }
+ if (!atomic_load_acquire(&ctx->want_reload)) {
+ LOCK(&ctx->readylock);
+ WAIT(&ctx->ready, &ctx->readylock);
+ UNLOCK(&ctx->readylock);
+ }
+ }
+ if (atomic_compare_exchange_strong_acq_rel(
+ &ctx->want_reload, &(bool){ true }, false))
+ {
+ return (ISC_R_RELOAD);
+ }
+
+ if (atomic_load_acquire(&ctx->want_shutdown) &&
+ atomic_load_acquire(&ctx->blocked))
+ {
+ exit(1);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_app_run(void) {
+ isc_result_t result;
+
+ atomic_compare_exchange_enforced(&is_running, &(bool){ false }, true);
+
+ result = isc_app_ctxrun(&isc_g_appctx);
+ atomic_store_release(&is_running, false);
+
+ return (result);
+}
+
+bool
+isc_app_isrunning(void) {
+ return (atomic_load_acquire(&is_running));
+}
+
+void
+isc_app_ctxshutdown(isc_appctx_t *ctx) {
+ REQUIRE(VALID_APPCTX(ctx));
+
+ REQUIRE(atomic_load_acquire(&ctx->running));
+
+ /* If ctx->shutdown_requested == true, we are already shutting
+ * down and we want to just bail out.
+ */
+ if (atomic_compare_exchange_strong_acq_rel(&ctx->shutdown_requested,
+ &(bool){ false }, true))
+ {
+ if (ctx != &isc_g_appctx) {
+ /* Tool using multiple contexts */
+ atomic_store_release(&ctx->want_shutdown, true);
+ SIGNAL(&ctx->ready);
+ } else {
+ /* Normal single BIND9 context */
+ if (kill(getpid(), SIGTERM) < 0) {
+ FATAL_SYSERROR(errno, "kill");
+ }
+ }
+ }
+}
+
+void
+isc_app_shutdown(void) {
+ isc_app_ctxshutdown(&isc_g_appctx);
+}
+
+void
+isc_app_ctxsuspend(isc_appctx_t *ctx) {
+ REQUIRE(VALID_APPCTX(ctx));
+
+ REQUIRE(atomic_load(&ctx->running));
+
+ /*
+ * Don't send the reload signal if we're shutting down.
+ */
+ if (!atomic_load_acquire(&ctx->shutdown_requested)) {
+ if (ctx != &isc_g_appctx) {
+ /* Tool using multiple contexts */
+ atomic_store_release(&ctx->want_reload, true);
+ SIGNAL(&ctx->ready);
+ } else {
+ /* Normal single BIND9 context */
+ if (kill(getpid(), SIGHUP) < 0) {
+ FATAL_SYSERROR(errno, "kill");
+ }
+ }
+ }
+}
+
+void
+isc_app_reload(void) {
+ isc_app_ctxsuspend(&isc_g_appctx);
+}
+
+void
+isc_app_ctxfinish(isc_appctx_t *ctx) {
+ REQUIRE(VALID_APPCTX(ctx));
+
+ isc_mutex_destroy(&ctx->lock);
+ isc_mutex_destroy(&ctx->readylock);
+ isc_condition_destroy(&ctx->ready);
+}
+
+void
+isc_app_finish(void) {
+ isc_app_ctxfinish(&isc_g_appctx);
+}
+
+void
+isc_app_block(void) {
+ sigset_t sset;
+
+ REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
+
+ atomic_compare_exchange_enforced(&isc_g_appctx.blocked,
+ &(bool){ false }, true);
+
+ blockedthread = pthread_self();
+ RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
+ sigaddset(&sset, SIGINT) == 0 &&
+ sigaddset(&sset, SIGTERM) == 0);
+ RUNTIME_CHECK(pthread_sigmask(SIG_UNBLOCK, &sset, NULL) == 0);
+}
+
+void
+isc_app_unblock(void) {
+ sigset_t sset;
+
+ REQUIRE(atomic_load_acquire(&isc_g_appctx.running));
+ REQUIRE(blockedthread == pthread_self());
+
+ atomic_compare_exchange_enforced(&isc_g_appctx.blocked, &(bool){ true },
+ false);
+
+ RUNTIME_CHECK(sigemptyset(&sset) == 0 &&
+ sigaddset(&sset, SIGINT) == 0 &&
+ sigaddset(&sset, SIGTERM) == 0);
+ RUNTIME_CHECK(pthread_sigmask(SIG_BLOCK, &sset, NULL) == 0);
+}
+
+isc_result_t
+isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp) {
+ isc_appctx_t *ctx;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(ctxp != NULL && *ctxp == NULL);
+
+ ctx = isc_mem_get(mctx, sizeof(*ctx));
+ *ctx = (isc_appctx_t){ .magic = 0 };
+
+ isc_mem_attach(mctx, &ctx->mctx);
+ ctx->magic = APPCTX_MAGIC;
+
+ *ctxp = ctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_appctx_destroy(isc_appctx_t **ctxp) {
+ isc_appctx_t *ctx;
+
+ REQUIRE(ctxp != NULL);
+ ctx = *ctxp;
+ *ctxp = NULL;
+ REQUIRE(VALID_APPCTX(ctx));
+
+ ctx->magic = 0;
+
+ isc_mem_putanddetach(&ctx->mctx, ctx, sizeof(*ctx));
+}
diff --git a/lib/isc/assertions.c b/lib/isc/assertions.c
new file mode 100644
index 0000000..e9a7935
--- /dev/null
+++ b/lib/isc/assertions.c
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <isc/assertions.h>
+#include <isc/backtrace.h>
+#include <isc/print.h>
+#include <isc/result.h>
+
+/*
+ * The maximum number of stack frames to dump on assertion failure.
+ */
+#ifndef BACKTRACE_MAXFRAME
+#define BACKTRACE_MAXFRAME 128
+#endif /* ifndef BACKTRACE_MAXFRAME */
+
+/*%
+ * Forward.
+ */
+static void
+default_callback(const char *, int, isc_assertiontype_t, const char *);
+
+static isc_assertioncallback_t isc_assertion_failed_cb = default_callback;
+
+/*%
+ * Public.
+ */
+
+/*% assertion failed handler */
+/* coverity[+kill] */
+void
+isc_assertion_failed(const char *file, int line, isc_assertiontype_t type,
+ const char *cond) {
+ isc_assertion_failed_cb(file, line, type, cond);
+ abort();
+}
+
+/*% Set callback. */
+void
+isc_assertion_setcallback(isc_assertioncallback_t cb) {
+ if (cb == NULL) {
+ isc_assertion_failed_cb = default_callback;
+ } else {
+ isc_assertion_failed_cb = cb;
+ }
+}
+
+/*% Type to Text */
+const char *
+isc_assertion_typetotext(isc_assertiontype_t type) {
+ const char *result;
+
+ /*
+ * These strings have purposefully not been internationalized
+ * because they are considered to essentially be keywords of
+ * the ISC development environment.
+ */
+ switch (type) {
+ case isc_assertiontype_require:
+ result = "REQUIRE";
+ break;
+ case isc_assertiontype_ensure:
+ result = "ENSURE";
+ break;
+ case isc_assertiontype_insist:
+ result = "INSIST";
+ break;
+ case isc_assertiontype_invariant:
+ result = "INVARIANT";
+ break;
+ default:
+ result = "UNKNOWN";
+ }
+ return (result);
+}
+
+/*
+ * Private.
+ */
+
+static void
+default_callback(const char *file, int line, isc_assertiontype_t type,
+ const char *cond) {
+ void *tracebuf[BACKTRACE_MAXFRAME];
+ int nframes = isc_backtrace(tracebuf, BACKTRACE_MAXFRAME);
+
+ fprintf(stderr, "%s:%d: %s(%s) failed%s\n", file, line,
+ isc_assertion_typetotext(type), cond,
+ (nframes > 0) ? ", back trace" : ".");
+
+ if (nframes > 0) {
+ isc_backtrace_symbols_fd(tracebuf, nframes, fileno(stderr));
+ }
+
+ fflush(stderr);
+}
diff --git a/lib/isc/astack.c b/lib/isc/astack.c
new file mode 100644
index 0000000..484ef26
--- /dev/null
+++ b/lib/isc/astack.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <isc/astack.h>
+#include <isc/atomic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+struct isc_astack {
+ isc_mem_t *mctx;
+ size_t size;
+ size_t pos;
+ isc_mutex_t lock;
+ uintptr_t nodes[];
+};
+
+isc_astack_t *
+isc_astack_new(isc_mem_t *mctx, size_t size) {
+ isc_astack_t *stack = isc_mem_get(
+ mctx, sizeof(isc_astack_t) + size * sizeof(uintptr_t));
+
+ *stack = (isc_astack_t){
+ .size = size,
+ };
+ isc_mem_attach(mctx, &stack->mctx);
+ memset(stack->nodes, 0, size * sizeof(uintptr_t));
+ isc_mutex_init(&stack->lock);
+ return (stack);
+}
+
+bool
+isc_astack_trypush(isc_astack_t *stack, void *obj) {
+ if (!isc_mutex_trylock(&stack->lock)) {
+ if (stack->pos >= stack->size) {
+ UNLOCK(&stack->lock);
+ return (false);
+ }
+ stack->nodes[stack->pos++] = (uintptr_t)obj;
+ UNLOCK(&stack->lock);
+ return (true);
+ } else {
+ return (false);
+ }
+}
+
+void *
+isc_astack_pop(isc_astack_t *stack) {
+ LOCK(&stack->lock);
+ uintptr_t rv;
+ if (stack->pos == 0) {
+ rv = 0;
+ } else {
+ rv = stack->nodes[--stack->pos];
+ }
+ UNLOCK(&stack->lock);
+ return ((void *)rv);
+}
+
+void
+isc_astack_destroy(isc_astack_t *stack) {
+ LOCK(&stack->lock);
+ REQUIRE(stack->pos == 0);
+ UNLOCK(&stack->lock);
+
+ isc_mutex_destroy(&stack->lock);
+
+ isc_mem_putanddetach(&stack->mctx, stack,
+ sizeof(struct isc_astack) +
+ stack->size * sizeof(uintptr_t));
+}
diff --git a/lib/isc/backtrace.c b/lib/isc/backtrace.c
new file mode 100644
index 0000000..3ac23c8
--- /dev/null
+++ b/lib/isc/backtrace.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+#include <string.h>
+#ifdef HAVE_BACKTRACE_SYMBOLS
+#include <execinfo.h>
+#endif /* HAVE_BACKTRACE_SYMBOLS */
+
+#include <isc/backtrace.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#if HAVE_BACKTRACE_SYMBOLS
+int
+isc_backtrace(void **addrs, int maxaddrs) {
+ int n;
+
+ /*
+ * Validate the arguments: intentionally avoid using REQUIRE().
+ * See notes in backtrace.h.
+ */
+ if (addrs == NULL || maxaddrs <= 0) {
+ return (-1);
+ }
+
+ /*
+ * backtrace(3) includes this function itself in the address array,
+ * which should be eliminated from the returned sequence.
+ */
+ n = backtrace(addrs, maxaddrs);
+ if (n < 2) {
+ return (-1);
+ }
+ n--;
+ memmove(addrs, &addrs[1], sizeof(addrs[0]) * n);
+
+ return (n);
+}
+
+char **
+isc_backtrace_symbols(void *const *buffer, int size) {
+ return (backtrace_symbols(buffer, size));
+}
+
+void
+isc_backtrace_symbols_fd(void *const *buffer, int size, int fd) {
+ backtrace_symbols_fd(buffer, size, fd);
+}
+
+#else /* HAVE_BACKTRACE_SYMBOLS */
+
+int
+isc_backtrace(void **addrs, int maxaddrs) {
+ UNUSED(addrs);
+ UNUSED(maxaddrs);
+
+ return (-1);
+}
+
+char **
+isc_backtrace_symbols(void *const *buffer, int size) {
+ UNUSED(buffer);
+ UNUSED(size);
+
+ return (NULL);
+}
+
+void
+isc_backtrace_symbols_fd(void *const *buffer, int size, int fd) {
+ UNUSED(buffer);
+ UNUSED(size);
+ UNUSED(fd);
+}
+
+#endif /* HAVE_BACKTRACE_SYMBOLS */
diff --git a/lib/isc/base32.c b/lib/isc/base32.c
new file mode 100644
index 0000000..90c37c7
--- /dev/null
+++ b/lib/isc/base32.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.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/base32.h>
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+/*@{*/
+/*!
+ * These static functions are also present in lib/dns/rdata.c. I'm not
+ * sure where they should go. -- bwelling
+ */
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target);
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
+
+/*@}*/
+
+static const char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="
+ "abcdefghijklmnopqrstuvwxyz234567";
+static const char base32hex[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV="
+ "0123456789abcdefghijklmnopqrstuv";
+
+static isc_result_t
+base32_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target, const char base[], char pad) {
+ char buf[9];
+ unsigned int loops = 0;
+
+ if (wordlength >= 0 && wordlength < 8) {
+ wordlength = 8;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ while (source->length > 0) {
+ buf[0] = base[((source->base[0] >> 3) & 0x1f)]; /* 5 + */
+ if (source->length == 1) {
+ buf[1] = base[(source->base[0] << 2) & 0x1c];
+ buf[2] = buf[3] = buf[4] = pad;
+ buf[5] = buf[6] = buf[7] = pad;
+ RETERR(str_totext(buf, target));
+ break;
+ }
+ buf[1] = base[((source->base[0] << 2) & 0x1c) | /* 3 = 8 */
+ ((source->base[1] >> 6) & 0x03)]; /* 2 + */
+ buf[2] = base[((source->base[1] >> 1) & 0x1f)]; /* 5 + */
+ if (source->length == 2) {
+ buf[3] = base[(source->base[1] << 4) & 0x10];
+ buf[4] = buf[5] = buf[6] = buf[7] = pad;
+ RETERR(str_totext(buf, target));
+ break;
+ }
+ buf[3] = base[((source->base[1] << 4) & 0x10) | /* 1 = 8 */
+ ((source->base[2] >> 4) & 0x0f)]; /* 4 + */
+ if (source->length == 3) {
+ buf[4] = base[(source->base[2] << 1) & 0x1e];
+ buf[5] = buf[6] = buf[7] = pad;
+ RETERR(str_totext(buf, target));
+ break;
+ }
+ buf[4] = base[((source->base[2] << 1) & 0x1e) | /* 4 = 8 */
+ ((source->base[3] >> 7) & 0x01)]; /* 1 + */
+ buf[5] = base[((source->base[3] >> 2) & 0x1f)]; /* 5 + */
+ if (source->length == 4) {
+ buf[6] = base[(source->base[3] << 3) & 0x18];
+ buf[7] = pad;
+ RETERR(str_totext(buf, target));
+ break;
+ }
+ buf[6] = base[((source->base[3] << 3) & 0x18) | /* 2 = 8 */
+ ((source->base[4] >> 5) & 0x07)]; /* 3 + */
+ buf[7] = base[source->base[4] & 0x1f]; /* 5 = 8 */
+ RETERR(str_totext(buf, target));
+ isc_region_consume(source, 5);
+
+ loops++;
+ if (source->length != 0 && wordlength >= 0 &&
+ (int)((loops + 1) * 8) >= wordlength)
+ {
+ loops = 0;
+ RETERR(str_totext(wordbreak, target));
+ }
+ }
+ if (source->length > 0) {
+ isc_region_consume(source, source->length);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base32_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target) {
+ return (base32_totext(source, wordlength, wordbreak, target, base32,
+ '='));
+}
+
+isc_result_t
+isc_base32hex_totext(isc_region_t *source, int wordlength,
+ const char *wordbreak, isc_buffer_t *target) {
+ return (base32_totext(source, wordlength, wordbreak, target, base32hex,
+ '='));
+}
+
+isc_result_t
+isc_base32hexnp_totext(isc_region_t *source, int wordlength,
+ const char *wordbreak, isc_buffer_t *target) {
+ return (base32_totext(source, wordlength, wordbreak, target, base32hex,
+ 0));
+}
+
+/*%
+ * State of a base32 decoding process in progress.
+ */
+typedef struct {
+ int length; /*%< Desired length of binary data or -1 */
+ isc_buffer_t *target; /*%< Buffer for resulting binary data */
+ int digits; /*%< Number of buffered base32 digits */
+ bool seen_end; /*%< True if "=" end marker seen */
+ int val[8];
+ const char *base; /*%< Which encoding we are using */
+ int seen_32; /*%< Number of significant bytes if non
+ * zero */
+ bool pad; /*%< Expect padding */
+} base32_decode_ctx_t;
+
+static void
+base32_decode_init(base32_decode_ctx_t *ctx, int length, const char base[],
+ bool pad, isc_buffer_t *target) {
+ ctx->digits = 0;
+ ctx->seen_end = false;
+ ctx->seen_32 = 0;
+ ctx->length = length;
+ ctx->target = target;
+ ctx->base = base;
+ ctx->pad = pad;
+}
+
+static isc_result_t
+base32_decode_char(base32_decode_ctx_t *ctx, int c) {
+ const char *s;
+ unsigned int last;
+
+ if (ctx->seen_end) {
+ return (ISC_R_BADBASE32);
+ }
+ if ((s = strchr(ctx->base, c)) == NULL) {
+ return (ISC_R_BADBASE32);
+ }
+ last = (unsigned int)(s - ctx->base);
+
+ /*
+ * Handle lower case.
+ */
+ if (last > 32) {
+ last -= 33;
+ }
+
+ /*
+ * Check that padding is contiguous.
+ */
+ if (last != 32 && ctx->seen_32 != 0) {
+ return (ISC_R_BADBASE32);
+ }
+
+ /*
+ * If padding is not permitted flag padding as a error.
+ */
+ if (last == 32 && !ctx->pad) {
+ return (ISC_R_BADBASE32);
+ }
+
+ /*
+ * Check that padding starts at the right place and that
+ * bits that should be zero are.
+ * Record how many significant bytes in answer (seen_32).
+ */
+ if (last == 32 && ctx->seen_32 == 0) {
+ switch (ctx->digits) {
+ case 0:
+ case 1:
+ return (ISC_R_BADBASE32);
+ case 2:
+ if ((ctx->val[1] & 0x03) != 0) {
+ return (ISC_R_BADBASE32);
+ }
+ ctx->seen_32 = 1;
+ break;
+ case 3:
+ return (ISC_R_BADBASE32);
+ case 4:
+ if ((ctx->val[3] & 0x0f) != 0) {
+ return (ISC_R_BADBASE32);
+ }
+ ctx->seen_32 = 2;
+ break;
+ case 5:
+ if ((ctx->val[4] & 0x01) != 0) {
+ return (ISC_R_BADBASE32);
+ }
+ ctx->seen_32 = 3;
+ break;
+ case 6:
+ return (ISC_R_BADBASE32);
+ case 7:
+ if ((ctx->val[6] & 0x07) != 0) {
+ return (ISC_R_BADBASE32);
+ }
+ ctx->seen_32 = 4;
+ break;
+ }
+ }
+
+ /*
+ * Zero fill pad values.
+ */
+ ctx->val[ctx->digits++] = (last == 32) ? 0 : last;
+
+ if (ctx->digits == 8) {
+ int n = 5;
+ unsigned char buf[5];
+
+ if (ctx->seen_32 != 0) {
+ ctx->seen_end = true;
+ n = ctx->seen_32;
+ }
+ buf[0] = (ctx->val[0] << 3) | (ctx->val[1] >> 2);
+ buf[1] = (ctx->val[1] << 6) | (ctx->val[2] << 1) |
+ (ctx->val[3] >> 4);
+ buf[2] = (ctx->val[3] << 4) | (ctx->val[4] >> 1);
+ buf[3] = (ctx->val[4] << 7) | (ctx->val[5] << 2) |
+ (ctx->val[6] >> 3);
+ buf[4] = (ctx->val[6] << 5) | (ctx->val[7]);
+ RETERR(mem_tobuffer(ctx->target, buf, n));
+ if (ctx->length >= 0) {
+ if (n > ctx->length) {
+ return (ISC_R_BADBASE32);
+ } else {
+ ctx->length -= n;
+ }
+ }
+ ctx->digits = 0;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+base32_decode_finish(base32_decode_ctx_t *ctx) {
+ if (ctx->length > 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ /*
+ * Add missing padding if required.
+ */
+ if (!ctx->pad && ctx->digits != 0) {
+ ctx->pad = true;
+ do {
+ RETERR(base32_decode_char(ctx, '='));
+ } while (ctx->digits != 0);
+ }
+ if (ctx->digits != 0) {
+ return (ISC_R_BADBASE32);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+base32_tobuffer(isc_lex_t *lexer, const char base[], bool pad,
+ isc_buffer_t *target, int length) {
+ unsigned int before, after;
+ base32_decode_ctx_t ctx;
+ isc_textregion_t *tr;
+ isc_token_t token;
+ bool eol;
+
+ REQUIRE(length >= -2);
+
+ base32_decode_init(&ctx, length, base, pad, target);
+
+ before = isc_buffer_usedlength(target);
+ while (!ctx.seen_end && (ctx.length != 0)) {
+ unsigned int i;
+
+ if (length > 0) {
+ eol = false;
+ } else {
+ eol = true;
+ }
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, eol));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ tr = &token.value.as_textregion;
+ for (i = 0; i < tr->length; i++) {
+ RETERR(base32_decode_char(&ctx, tr->base[i]));
+ }
+ }
+ after = isc_buffer_usedlength(target);
+ if (ctx.length < 0 && !ctx.seen_end) {
+ isc_lex_ungettoken(lexer, &token);
+ }
+ RETERR(base32_decode_finish(&ctx));
+ if (length == -2 && before == after) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base32_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
+ return (base32_tobuffer(lexer, base32, true, target, length));
+}
+
+isc_result_t
+isc_base32hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
+ return (base32_tobuffer(lexer, base32hex, true, target, length));
+}
+
+isc_result_t
+isc_base32hexnp_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
+ return (base32_tobuffer(lexer, base32hex, false, target, length));
+}
+
+static isc_result_t
+base32_decodestring(const char *cstr, const char base[], bool pad,
+ isc_buffer_t *target) {
+ base32_decode_ctx_t ctx;
+
+ base32_decode_init(&ctx, -1, base, pad, target);
+ for (;;) {
+ int c = *cstr++;
+ if (c == '\0') {
+ break;
+ }
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+ continue;
+ }
+ RETERR(base32_decode_char(&ctx, c));
+ }
+ RETERR(base32_decode_finish(&ctx));
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base32_decodestring(const char *cstr, isc_buffer_t *target) {
+ return (base32_decodestring(cstr, base32, true, target));
+}
+
+isc_result_t
+isc_base32hex_decodestring(const char *cstr, isc_buffer_t *target) {
+ return (base32_decodestring(cstr, base32hex, true, target));
+}
+
+isc_result_t
+isc_base32hexnp_decodestring(const char *cstr, isc_buffer_t *target) {
+ return (base32_decodestring(cstr, base32hex, false, target));
+}
+
+static isc_result_t
+base32_decoderegion(isc_region_t *source, const char base[], bool pad,
+ isc_buffer_t *target) {
+ base32_decode_ctx_t ctx;
+
+ base32_decode_init(&ctx, -1, base, pad, target);
+ while (source->length != 0) {
+ int c = *source->base;
+ RETERR(base32_decode_char(&ctx, c));
+ isc_region_consume(source, 1);
+ }
+ RETERR(base32_decode_finish(&ctx));
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base32_decoderegion(isc_region_t *source, isc_buffer_t *target) {
+ return (base32_decoderegion(source, base32, true, target));
+}
+
+isc_result_t
+isc_base32hex_decoderegion(isc_region_t *source, isc_buffer_t *target) {
+ return (base32_decoderegion(source, base32hex, true, target));
+}
+
+isc_result_t
+isc_base32hexnp_decoderegion(isc_region_t *source, isc_buffer_t *target) {
+ return (base32_decoderegion(source, base32hex, false, target));
+}
+
+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
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
+ isc_region_t tr;
+
+ isc_buffer_availableregion(target, &tr);
+ if (length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, base, length);
+ isc_buffer_add(target, length);
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/base64.c b/lib/isc/base64.c
new file mode 100644
index 0000000..958ef4f
--- /dev/null
+++ b/lib/isc/base64.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/base64.h>
+#include <isc/buffer.h>
+#include <isc/lex.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+/*@{*/
+/*!
+ * These static functions are also present in lib/dns/rdata.c. I'm not
+ * sure where they should go. -- bwelling
+ */
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target);
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
+
+static const char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvw"
+ "xyz0123456789+/=";
+/*@}*/
+
+isc_result_t
+isc_base64_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target) {
+ char buf[5];
+ unsigned int loops = 0;
+
+ if (wordlength < 4) {
+ wordlength = 4;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ while (source->length > 2) {
+ buf[0] = base64[(source->base[0] >> 2) & 0x3f];
+ buf[1] = base64[((source->base[0] << 4) & 0x30) |
+ ((source->base[1] >> 4) & 0x0f)];
+ buf[2] = base64[((source->base[1] << 2) & 0x3c) |
+ ((source->base[2] >> 6) & 0x03)];
+ buf[3] = base64[source->base[2] & 0x3f];
+ RETERR(str_totext(buf, target));
+ isc_region_consume(source, 3);
+
+ loops++;
+ if (source->length != 0 && (int)((loops + 1) * 4) >= wordlength)
+ {
+ loops = 0;
+ RETERR(str_totext(wordbreak, target));
+ }
+ }
+ if (source->length == 2) {
+ buf[0] = base64[(source->base[0] >> 2) & 0x3f];
+ buf[1] = base64[((source->base[0] << 4) & 0x30) |
+ ((source->base[1] >> 4) & 0x0f)];
+ buf[2] = base64[((source->base[1] << 2) & 0x3c)];
+ buf[3] = '=';
+ RETERR(str_totext(buf, target));
+ isc_region_consume(source, 2);
+ } else if (source->length == 1) {
+ buf[0] = base64[(source->base[0] >> 2) & 0x3f];
+ buf[1] = base64[((source->base[0] << 4) & 0x30)];
+ buf[2] = buf[3] = '=';
+ RETERR(str_totext(buf, target));
+ isc_region_consume(source, 1);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * State of a base64 decoding process in progress.
+ */
+typedef struct {
+ int length; /*%< Desired length of binary data or -1 */
+ isc_buffer_t *target; /*%< Buffer for resulting binary data */
+ int digits; /*%< Number of buffered base64 digits */
+ bool seen_end; /*%< True if "=" end marker seen */
+ int val[4];
+} base64_decode_ctx_t;
+
+static void
+base64_decode_init(base64_decode_ctx_t *ctx, int length, isc_buffer_t *target) {
+ ctx->digits = 0;
+ ctx->seen_end = false;
+ ctx->length = length;
+ ctx->target = target;
+}
+
+static isc_result_t
+base64_decode_char(base64_decode_ctx_t *ctx, int c) {
+ const char *s;
+
+ if (ctx->seen_end) {
+ return (ISC_R_BADBASE64);
+ }
+ if ((s = strchr(base64, c)) == NULL) {
+ return (ISC_R_BADBASE64);
+ }
+ ctx->val[ctx->digits++] = (int)(s - base64);
+ if (ctx->digits == 4) {
+ int n;
+ unsigned char buf[3];
+ if (ctx->val[0] == 64 || ctx->val[1] == 64) {
+ return (ISC_R_BADBASE64);
+ }
+ if (ctx->val[2] == 64 && ctx->val[3] != 64) {
+ return (ISC_R_BADBASE64);
+ }
+ /*
+ * Check that bits that should be zero are.
+ */
+ if (ctx->val[2] == 64 && (ctx->val[1] & 0xf) != 0) {
+ return (ISC_R_BADBASE64);
+ }
+ /*
+ * We don't need to test for ctx->val[2] != 64 as
+ * the bottom two bits of 64 are zero.
+ */
+ if (ctx->val[3] == 64 && (ctx->val[2] & 0x3) != 0) {
+ return (ISC_R_BADBASE64);
+ }
+ n = (ctx->val[2] == 64) ? 1 : (ctx->val[3] == 64) ? 2 : 3;
+ if (n != 3) {
+ ctx->seen_end = true;
+ if (ctx->val[2] == 64) {
+ ctx->val[2] = 0;
+ }
+ if (ctx->val[3] == 64) {
+ ctx->val[3] = 0;
+ }
+ }
+ buf[0] = (ctx->val[0] << 2) | (ctx->val[1] >> 4);
+ buf[1] = (ctx->val[1] << 4) | (ctx->val[2] >> 2);
+ buf[2] = (ctx->val[2] << 6) | (ctx->val[3]);
+ RETERR(mem_tobuffer(ctx->target, buf, n));
+ if (ctx->length >= 0) {
+ if (n > ctx->length) {
+ return (ISC_R_BADBASE64);
+ } else {
+ ctx->length -= n;
+ }
+ }
+ ctx->digits = 0;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+base64_decode_finish(base64_decode_ctx_t *ctx) {
+ if (ctx->length > 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (ctx->digits != 0) {
+ return (ISC_R_BADBASE64);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
+ unsigned int before, after;
+ base64_decode_ctx_t ctx;
+ isc_textregion_t *tr;
+ isc_token_t token;
+ bool eol;
+
+ REQUIRE(length >= -2);
+
+ base64_decode_init(&ctx, length, target);
+
+ before = isc_buffer_usedlength(target);
+ while (!ctx.seen_end && (ctx.length != 0)) {
+ unsigned int i;
+
+ if (length > 0) {
+ eol = false;
+ } else {
+ eol = true;
+ }
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, eol));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ tr = &token.value.as_textregion;
+ for (i = 0; i < tr->length; i++) {
+ RETERR(base64_decode_char(&ctx, tr->base[i]));
+ }
+ }
+ after = isc_buffer_usedlength(target);
+ if (ctx.length < 0 && !ctx.seen_end) {
+ isc_lex_ungettoken(lexer, &token);
+ }
+ RETERR(base64_decode_finish(&ctx));
+ if (length == -2 && before == after) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_base64_decodestring(const char *cstr, isc_buffer_t *target) {
+ base64_decode_ctx_t ctx;
+
+ base64_decode_init(&ctx, -1, target);
+ for (;;) {
+ int c = *cstr++;
+ if (c == '\0') {
+ break;
+ }
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+ continue;
+ }
+ RETERR(base64_decode_char(&ctx, c));
+ }
+ RETERR(base64_decode_finish(&ctx));
+ return (ISC_R_SUCCESS);
+}
+
+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
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
+ isc_region_t tr;
+
+ isc_buffer_availableregion(target, &tr);
+ if (length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, base, length);
+ isc_buffer_add(target, length);
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/buffer.c b/lib/isc/buffer.c
new file mode 100644
index 0000000..e7e84df
--- /dev/null
+++ b/lib/isc/buffer.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <isc/buffer.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+void
+isc_buffer_reinit(isc_buffer_t *b, void *base, unsigned int length) {
+ /*
+ * Re-initialize the buffer enough to reconfigure the base of the
+ * buffer. We will swap in the new buffer, after copying any
+ * data we contain into the new buffer and adjusting all of our
+ * internal pointers.
+ *
+ * The buffer must not be smaller than the length of the original
+ * buffer.
+ */
+ REQUIRE(b->length <= length);
+ REQUIRE(base != NULL);
+ REQUIRE(!b->autore);
+
+ if (b->length > 0U) {
+ (void)memmove(base, b->base, b->length);
+ }
+
+ b->base = base;
+ b->length = length;
+}
+
+void
+isc_buffer_setautorealloc(isc_buffer_t *b, bool enable) {
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(b->mctx != NULL);
+ b->autore = enable;
+}
+
+void
+isc_buffer_compact(isc_buffer_t *b) {
+ unsigned int length;
+ void *src;
+
+ /*
+ * Compact the used region by moving the remaining region so it occurs
+ * at the start of the buffer. The used region is shrunk by the size
+ * of the consumed region, and the consumed region is then made empty.
+ */
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+
+ src = isc_buffer_current(b);
+ length = isc_buffer_remaininglength(b);
+ if (length > 0U) {
+ (void)memmove(b->base, src, (size_t)length);
+ }
+
+ if (b->active > b->current) {
+ b->active -= b->current;
+ } else {
+ b->active = 0;
+ }
+ b->current = 0;
+ b->used = length;
+}
+
+uint8_t
+isc_buffer_getuint8(isc_buffer_t *b) {
+ unsigned char *cp;
+ uint8_t result;
+
+ /*
+ * Read an unsigned 8-bit integer from 'b' and return it.
+ */
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(b->used - b->current >= 1);
+
+ cp = isc_buffer_current(b);
+ b->current += 1;
+ result = ((uint8_t)(cp[0]));
+
+ return (result);
+}
+
+uint16_t
+isc_buffer_getuint16(isc_buffer_t *b) {
+ unsigned char *cp;
+ uint16_t result;
+
+ /*
+ * Read an unsigned 16-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ */
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(b->used - b->current >= 2);
+
+ cp = isc_buffer_current(b);
+ b->current += 2;
+ result = ((unsigned int)(cp[0])) << 8;
+ result |= ((unsigned int)(cp[1]));
+
+ return (result);
+}
+
+uint32_t
+isc_buffer_getuint32(isc_buffer_t *b) {
+ unsigned char *cp;
+ uint32_t result;
+
+ /*
+ * Read an unsigned 32-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ */
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(b->used - b->current >= 4);
+
+ cp = isc_buffer_current(b);
+ b->current += 4;
+ result = ((unsigned int)(cp[0])) << 24;
+ result |= ((unsigned int)(cp[1])) << 16;
+ result |= ((unsigned int)(cp[2])) << 8;
+ result |= ((unsigned int)(cp[3]));
+
+ return (result);
+}
+
+uint64_t
+isc_buffer_getuint48(isc_buffer_t *b) {
+ unsigned char *cp;
+ uint64_t result;
+
+ /*
+ * Read an unsigned 48-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ */
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(b->used - b->current >= 6);
+
+ cp = isc_buffer_current(b);
+ b->current += 6;
+ result = ((int64_t)(cp[0])) << 40;
+ result |= ((int64_t)(cp[1])) << 32;
+ result |= ((int64_t)(cp[2])) << 24;
+ result |= ((int64_t)(cp[3])) << 16;
+ result |= ((int64_t)(cp[4])) << 8;
+ result |= ((int64_t)(cp[5]));
+
+ return (result);
+}
+
+void
+isc_buffer_putdecint(isc_buffer_t *b, int64_t v) {
+ unsigned int l = 0;
+ unsigned char *cp;
+ char buf[21];
+ isc_result_t result;
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+
+ /* xxxwpk do it more low-level way ? */
+ l = snprintf(buf, 21, "%" PRId64, v);
+ RUNTIME_CHECK(l <= 21);
+ if (b->autore) {
+ result = isc_buffer_reserve(&b, l);
+ REQUIRE(result == ISC_R_SUCCESS);
+ }
+ REQUIRE(isc_buffer_availablelength(b) >= l);
+
+ cp = isc_buffer_used(b);
+ memmove(cp, buf, l);
+ b->used += l;
+}
+
+isc_result_t
+isc_buffer_dup(isc_mem_t *mctx, isc_buffer_t **dstp, const isc_buffer_t *src) {
+ isc_buffer_t *dst = NULL;
+ isc_region_t region;
+ isc_result_t result;
+
+ REQUIRE(dstp != NULL && *dstp == NULL);
+ REQUIRE(ISC_BUFFER_VALID(src));
+
+ isc_buffer_usedregion(src, &region);
+
+ isc_buffer_allocate(mctx, &dst, region.length);
+
+ result = isc_buffer_copyregion(dst, &region);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS); /* NOSPACE is impossible */
+ *dstp = dst;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_buffer_copyregion(isc_buffer_t *b, const isc_region_t *r) {
+ isc_result_t result;
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+ REQUIRE(r != NULL);
+
+ if (b->autore) {
+ result = isc_buffer_reserve(&b, r->length);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (r->length > isc_buffer_availablelength(b)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ if (r->length > 0U) {
+ memmove(isc_buffer_used(b), r->base, r->length);
+ b->used += r->length;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer,
+ unsigned int length) {
+ REQUIRE(dynbuffer != NULL && *dynbuffer == NULL);
+
+ isc_buffer_t *dbuf = isc_mem_get(mctx, sizeof(isc_buffer_t));
+ unsigned char *bdata = isc_mem_get(mctx, length);
+
+ isc_buffer_init(dbuf, bdata, length);
+
+ ENSURE(ISC_BUFFER_VALID(dbuf));
+
+ dbuf->mctx = mctx;
+
+ *dynbuffer = dbuf;
+}
+
+isc_result_t
+isc_buffer_reserve(isc_buffer_t **dynbuffer, unsigned int size) {
+ size_t len;
+
+ REQUIRE(dynbuffer != NULL);
+ REQUIRE(ISC_BUFFER_VALID(*dynbuffer));
+
+ len = (*dynbuffer)->length;
+ if ((len - (*dynbuffer)->used) >= size) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if ((*dynbuffer)->mctx == NULL) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /* Round to nearest buffer size increment */
+ len = size + (*dynbuffer)->used;
+ len = (len + ISC_BUFFER_INCR - 1 - ((len - 1) % ISC_BUFFER_INCR));
+
+ /* Cap at UINT_MAX */
+ if (len > UINT_MAX) {
+ len = UINT_MAX;
+ }
+
+ if ((len - (*dynbuffer)->used) < size) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ (*dynbuffer)->base = isc_mem_reget((*dynbuffer)->mctx,
+ (*dynbuffer)->base,
+ (*dynbuffer)->length, len);
+ (*dynbuffer)->length = (unsigned int)len;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_buffer_free(isc_buffer_t **dynbuffer) {
+ isc_buffer_t *dbuf;
+ isc_mem_t *mctx;
+
+ REQUIRE(dynbuffer != NULL);
+ REQUIRE(ISC_BUFFER_VALID(*dynbuffer));
+ REQUIRE((*dynbuffer)->mctx != NULL);
+
+ dbuf = *dynbuffer;
+ *dynbuffer = NULL; /* destroy external reference */
+ mctx = dbuf->mctx;
+ dbuf->mctx = NULL;
+
+ isc_mem_put(mctx, dbuf->base, dbuf->length);
+ isc_buffer_invalidate(dbuf);
+ isc_mem_put(mctx, dbuf, sizeof(isc_buffer_t));
+}
+
+isc_result_t
+isc_buffer_printf(isc_buffer_t *b, const char *format, ...) {
+ va_list ap;
+ int n;
+ isc_result_t result;
+
+ REQUIRE(ISC_BUFFER_VALID(b));
+
+ va_start(ap, format);
+ n = vsnprintf(NULL, 0, format, ap);
+ va_end(ap);
+
+ if (n < 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ if (b->autore) {
+ result = isc_buffer_reserve(&b, n + 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (isc_buffer_availablelength(b) < (unsigned int)n + 1) {
+ return (ISC_R_NOSPACE);
+ }
+
+ va_start(ap, format);
+ n = vsnprintf(isc_buffer_used(b), n + 1, format, ap);
+ va_end(ap);
+
+ if (n < 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ b->used += n;
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/commandline.c b/lib/isc/commandline.c
new file mode 100644
index 0000000..1b4a33a
--- /dev/null
+++ b/lib/isc/commandline.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND BSD-3-Clause
+
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * Copyright (C) 1987, 1993, 1994 The Regents of the University of California.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file
+ * This file was adapted from the NetBSD project's source tree, RCS ID:
+ * NetBSD: getopt.c,v 1.15 1999/09/20 04:39:37 lukem Exp
+ *
+ * The primary change has been to rename items to the ISC namespace
+ * and format in the ISC coding style.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/commandline.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+/*% Index into parent argv vector. */
+int isc_commandline_index = 1;
+/*% Character checked for validity. */
+int isc_commandline_option;
+/*% Argument associated with option. */
+char *isc_commandline_argument;
+/*% For printing error messages. */
+char *isc_commandline_progname;
+/*% Print error messages. */
+bool isc_commandline_errprint = true;
+/*% Reset processing. */
+bool isc_commandline_reset = true;
+
+static char endopt = '\0';
+
+#define BADOPT '?'
+#define BADARG ':'
+#define ENDOPT &endopt
+
+/*!
+ * getopt --
+ * Parse argc/argv argument vector.
+ */
+int
+isc_commandline_parse(int argc, char *const *argv, const char *options) {
+ static char *place = ENDOPT;
+ const char *option; /* Index into *options of option. */
+
+ REQUIRE(argc >= 0 && argv != NULL && options != NULL);
+
+ /*
+ * Update scanning pointer, either because a reset was requested or
+ * the previous argv was finished.
+ */
+ if (isc_commandline_reset || *place == '\0') {
+ if (isc_commandline_reset) {
+ isc_commandline_index = 1;
+ isc_commandline_reset = false;
+ }
+
+ if (isc_commandline_progname == NULL) {
+ isc_commandline_progname = argv[0];
+ }
+
+ if (isc_commandline_index >= argc ||
+ *(place = argv[isc_commandline_index]) != '-')
+ {
+ /*
+ * Index out of range or points to non-option.
+ */
+ place = ENDOPT;
+ return (-1);
+ }
+
+ if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
+ /*
+ * Found '--' to signal end of options. Advance
+ * index to next argv, the first non-option.
+ */
+ isc_commandline_index++;
+ place = ENDOPT;
+ return (-1);
+ }
+ }
+
+ isc_commandline_option = *place++;
+ option = strchr(options, isc_commandline_option);
+
+ /*
+ * Ensure valid option has been passed as specified by options string.
+ * '-:' is never a valid command line option because it could not
+ * distinguish ':' from the argument specifier in the options string.
+ */
+ if (isc_commandline_option == ':' || option == NULL) {
+ if (*place == '\0') {
+ isc_commandline_index++;
+ }
+
+ if (isc_commandline_errprint && *options != ':') {
+ fprintf(stderr, "%s: illegal option -- %c\n",
+ isc_commandline_progname,
+ isc_commandline_option);
+ }
+
+ return (BADOPT);
+ }
+
+ if (*++option != ':') {
+ /*
+ * Option does not take an argument.
+ */
+ isc_commandline_argument = NULL;
+
+ /*
+ * Skip to next argv if at the end of the current argv.
+ */
+ if (*place == '\0') {
+ ++isc_commandline_index;
+ }
+ } else {
+ /*
+ * Option needs an argument.
+ */
+ if (*place != '\0') {
+ /*
+ * Option is in this argv, -D1 style.
+ */
+ isc_commandline_argument = place;
+ } else if (argc > ++isc_commandline_index) {
+ /*
+ * Option is next argv, -D 1 style.
+ */
+ isc_commandline_argument = argv[isc_commandline_index];
+ } else {
+ /*
+ * Argument needed, but no more argv.
+ */
+ place = ENDOPT;
+
+ /*
+ * Silent failure with "missing argument" return
+ * when ':' starts options string, per historical spec.
+ */
+ if (*options == ':') {
+ return (BADARG);
+ }
+
+ if (isc_commandline_errprint) {
+ fprintf(stderr,
+ "%s: option requires an argument -- "
+ "%c\n",
+ isc_commandline_progname,
+ isc_commandline_option);
+ }
+
+ return (BADOPT);
+ }
+
+ place = ENDOPT;
+
+ /*
+ * Point to argv that follows argument.
+ */
+ isc_commandline_index++;
+ }
+
+ return (isc_commandline_option);
+}
+
+isc_result_t
+isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp,
+ char ***argvp, unsigned int n) {
+ isc_result_t result;
+
+restart:
+ /* Discard leading whitespace. */
+ while (*s == ' ' || *s == '\t') {
+ s++;
+ }
+
+ if (*s == '\0') {
+ /* We have reached the end of the string. */
+ *argcp = n;
+ *argvp = isc_mem_get(mctx, n * sizeof(char *));
+ } else {
+ char *p = s;
+ while (*p != ' ' && *p != '\t' && *p != '\0' && *p != '{') {
+ if (*p == '\n') {
+ *p = ' ';
+ goto restart;
+ }
+ p++;
+ }
+
+ /* do "grouping", items between { and } are one arg */
+ if (*p == '{') {
+ char *t = p;
+ /*
+ * shift all characters to left by 1 to get rid of '{'
+ */
+ while (*t != '\0') {
+ t++;
+ *(t - 1) = *t;
+ }
+ while (*p != '\0' && *p != '}') {
+ p++;
+ }
+ /* get rid of '}' character */
+ if (*p == '}') {
+ *p = '\0';
+ p++;
+ }
+ /* normal case, no "grouping" */
+ } else if (*p != '\0') {
+ *p++ = '\0';
+ }
+
+ result = isc_commandline_strtoargv(mctx, p, argcp, argvp,
+ n + 1);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ (*argvp)[n] = s;
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/condition.c b/lib/isc/condition.c
new file mode 100644
index 0000000..18fab69
--- /dev/null
+++ b/lib/isc/condition.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <isc/condition.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+isc_result_t
+isc_condition_waituntil(isc_condition_t *c, isc_mutex_t *m, isc_time_t *t) {
+ int presult;
+ isc_result_t result;
+ struct timespec ts;
+
+ REQUIRE(c != NULL && m != NULL && t != NULL);
+
+ /*
+ * POSIX defines a timespec's tv_sec as time_t.
+ */
+ result = isc_time_secondsastimet(t, &ts.tv_sec);
+
+ /*
+ * If we have a range error ts.tv_sec is most probably a signed
+ * 32 bit value. Set ts.tv_sec to INT_MAX. This is a kludge.
+ */
+ if (result == ISC_R_RANGE) {
+ ts.tv_sec = INT_MAX;
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*!
+ * POSIX defines a timespec's tv_nsec as long. isc_time_nanoseconds
+ * ensures its return value is < 1 billion, which will fit in a long.
+ */
+ ts.tv_nsec = (long)isc_time_nanoseconds(t);
+
+ do {
+ presult = pthread_cond_timedwait(c, m, &ts);
+ if (presult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ if (presult == ETIMEDOUT) {
+ return (ISC_R_TIMEDOUT);
+ }
+ } while (presult == EINTR);
+
+ UNEXPECTED_SYSERROR(presult, "pthread_cond_timedwait()");
+ return (ISC_R_UNEXPECTED);
+}
diff --git a/lib/isc/counter.c b/lib/isc/counter.c
new file mode 100644
index 0000000..0c0ccd6
--- /dev/null
+++ b/lib/isc/counter.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include <isc/atomic.h>
+#include <isc/counter.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/util.h>
+
+#define COUNTER_MAGIC ISC_MAGIC('C', 'n', 't', 'r')
+#define VALID_COUNTER(r) ISC_MAGIC_VALID(r, COUNTER_MAGIC)
+
+struct isc_counter {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ atomic_uint_fast32_t limit;
+ atomic_uint_fast32_t used;
+};
+
+isc_result_t
+isc_counter_create(isc_mem_t *mctx, int limit, isc_counter_t **counterp) {
+ isc_counter_t *counter;
+
+ REQUIRE(counterp != NULL && *counterp == NULL);
+
+ counter = isc_mem_get(mctx, sizeof(*counter));
+
+ counter->mctx = NULL;
+ isc_mem_attach(mctx, &counter->mctx);
+
+ isc_refcount_init(&counter->references, 1);
+ atomic_init(&counter->limit, limit);
+ atomic_init(&counter->used, 0);
+
+ counter->magic = COUNTER_MAGIC;
+ *counterp = counter;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_counter_increment(isc_counter_t *counter) {
+ uint32_t used = atomic_fetch_add_relaxed(&counter->used, 1) + 1;
+ uint32_t limit = atomic_load_acquire(&counter->limit);
+
+ if (limit != 0 && used >= limit) {
+ return (ISC_R_QUOTA);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+isc_counter_used(isc_counter_t *counter) {
+ REQUIRE(VALID_COUNTER(counter));
+
+ return (atomic_load_acquire(&counter->used));
+}
+
+void
+isc_counter_setlimit(isc_counter_t *counter, int limit) {
+ REQUIRE(VALID_COUNTER(counter));
+
+ atomic_store(&counter->limit, limit);
+}
+
+void
+isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp) {
+ REQUIRE(VALID_COUNTER(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+destroy(isc_counter_t *counter) {
+ isc_refcount_destroy(&counter->references);
+ counter->magic = 0;
+ isc_mem_putanddetach(&counter->mctx, counter, sizeof(*counter));
+}
+
+void
+isc_counter_detach(isc_counter_t **counterp) {
+ isc_counter_t *counter;
+
+ REQUIRE(counterp != NULL && *counterp != NULL);
+ counter = *counterp;
+ *counterp = NULL;
+ REQUIRE(VALID_COUNTER(counter));
+
+ if (isc_refcount_decrement(&counter->references) == 1) {
+ destroy(counter);
+ }
+}
diff --git a/lib/isc/crc64.c b/lib/isc/crc64.c
new file mode 100644
index 0000000..4b6c213
--- /dev/null
+++ b/lib/isc/crc64.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/assertions.h>
+#include <isc/crc64.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+/*%<
+ * ECMA-182 CRC64 polynomial.
+ */
+static const uint64_t crc64_table[256] = {
+ 0x0000000000000000ULL, 0x42F0E1EBA9EA3693ULL, 0x85E1C3D753D46D26ULL,
+ 0xC711223CFA3E5BB5ULL, 0x493366450E42ECDFULL, 0x0BC387AEA7A8DA4CULL,
+ 0xCCD2A5925D9681F9ULL, 0x8E224479F47CB76AULL, 0x9266CC8A1C85D9BEULL,
+ 0xD0962D61B56FEF2DULL, 0x17870F5D4F51B498ULL, 0x5577EEB6E6BB820BULL,
+ 0xDB55AACF12C73561ULL, 0x99A54B24BB2D03F2ULL, 0x5EB4691841135847ULL,
+ 0x1C4488F3E8F96ED4ULL, 0x663D78FF90E185EFULL, 0x24CD9914390BB37CULL,
+ 0xE3DCBB28C335E8C9ULL, 0xA12C5AC36ADFDE5AULL, 0x2F0E1EBA9EA36930ULL,
+ 0x6DFEFF5137495FA3ULL, 0xAAEFDD6DCD770416ULL, 0xE81F3C86649D3285ULL,
+ 0xF45BB4758C645C51ULL, 0xB6AB559E258E6AC2ULL, 0x71BA77A2DFB03177ULL,
+ 0x334A9649765A07E4ULL, 0xBD68D2308226B08EULL, 0xFF9833DB2BCC861DULL,
+ 0x388911E7D1F2DDA8ULL, 0x7A79F00C7818EB3BULL, 0xCC7AF1FF21C30BDEULL,
+ 0x8E8A101488293D4DULL, 0x499B3228721766F8ULL, 0x0B6BD3C3DBFD506BULL,
+ 0x854997BA2F81E701ULL, 0xC7B97651866BD192ULL, 0x00A8546D7C558A27ULL,
+ 0x4258B586D5BFBCB4ULL, 0x5E1C3D753D46D260ULL, 0x1CECDC9E94ACE4F3ULL,
+ 0xDBFDFEA26E92BF46ULL, 0x990D1F49C77889D5ULL, 0x172F5B3033043EBFULL,
+ 0x55DFBADB9AEE082CULL, 0x92CE98E760D05399ULL, 0xD03E790CC93A650AULL,
+ 0xAA478900B1228E31ULL, 0xE8B768EB18C8B8A2ULL, 0x2FA64AD7E2F6E317ULL,
+ 0x6D56AB3C4B1CD584ULL, 0xE374EF45BF6062EEULL, 0xA1840EAE168A547DULL,
+ 0x66952C92ECB40FC8ULL, 0x2465CD79455E395BULL, 0x3821458AADA7578FULL,
+ 0x7AD1A461044D611CULL, 0xBDC0865DFE733AA9ULL, 0xFF3067B657990C3AULL,
+ 0x711223CFA3E5BB50ULL, 0x33E2C2240A0F8DC3ULL, 0xF4F3E018F031D676ULL,
+ 0xB60301F359DBE0E5ULL, 0xDA050215EA6C212FULL, 0x98F5E3FE438617BCULL,
+ 0x5FE4C1C2B9B84C09ULL, 0x1D14202910527A9AULL, 0x93366450E42ECDF0ULL,
+ 0xD1C685BB4DC4FB63ULL, 0x16D7A787B7FAA0D6ULL, 0x5427466C1E109645ULL,
+ 0x4863CE9FF6E9F891ULL, 0x0A932F745F03CE02ULL, 0xCD820D48A53D95B7ULL,
+ 0x8F72ECA30CD7A324ULL, 0x0150A8DAF8AB144EULL, 0x43A04931514122DDULL,
+ 0x84B16B0DAB7F7968ULL, 0xC6418AE602954FFBULL, 0xBC387AEA7A8DA4C0ULL,
+ 0xFEC89B01D3679253ULL, 0x39D9B93D2959C9E6ULL, 0x7B2958D680B3FF75ULL,
+ 0xF50B1CAF74CF481FULL, 0xB7FBFD44DD257E8CULL, 0x70EADF78271B2539ULL,
+ 0x321A3E938EF113AAULL, 0x2E5EB66066087D7EULL, 0x6CAE578BCFE24BEDULL,
+ 0xABBF75B735DC1058ULL, 0xE94F945C9C3626CBULL, 0x676DD025684A91A1ULL,
+ 0x259D31CEC1A0A732ULL, 0xE28C13F23B9EFC87ULL, 0xA07CF2199274CA14ULL,
+ 0x167FF3EACBAF2AF1ULL, 0x548F120162451C62ULL, 0x939E303D987B47D7ULL,
+ 0xD16ED1D631917144ULL, 0x5F4C95AFC5EDC62EULL, 0x1DBC74446C07F0BDULL,
+ 0xDAAD56789639AB08ULL, 0x985DB7933FD39D9BULL, 0x84193F60D72AF34FULL,
+ 0xC6E9DE8B7EC0C5DCULL, 0x01F8FCB784FE9E69ULL, 0x43081D5C2D14A8FAULL,
+ 0xCD2A5925D9681F90ULL, 0x8FDAB8CE70822903ULL, 0x48CB9AF28ABC72B6ULL,
+ 0x0A3B7B1923564425ULL, 0x70428B155B4EAF1EULL, 0x32B26AFEF2A4998DULL,
+ 0xF5A348C2089AC238ULL, 0xB753A929A170F4ABULL, 0x3971ED50550C43C1ULL,
+ 0x7B810CBBFCE67552ULL, 0xBC902E8706D82EE7ULL, 0xFE60CF6CAF321874ULL,
+ 0xE224479F47CB76A0ULL, 0xA0D4A674EE214033ULL, 0x67C58448141F1B86ULL,
+ 0x253565A3BDF52D15ULL, 0xAB1721DA49899A7FULL, 0xE9E7C031E063ACECULL,
+ 0x2EF6E20D1A5DF759ULL, 0x6C0603E6B3B7C1CAULL, 0xF6FAE5C07D3274CDULL,
+ 0xB40A042BD4D8425EULL, 0x731B26172EE619EBULL, 0x31EBC7FC870C2F78ULL,
+ 0xBFC9838573709812ULL, 0xFD39626EDA9AAE81ULL, 0x3A28405220A4F534ULL,
+ 0x78D8A1B9894EC3A7ULL, 0x649C294A61B7AD73ULL, 0x266CC8A1C85D9BE0ULL,
+ 0xE17DEA9D3263C055ULL, 0xA38D0B769B89F6C6ULL, 0x2DAF4F0F6FF541ACULL,
+ 0x6F5FAEE4C61F773FULL, 0xA84E8CD83C212C8AULL, 0xEABE6D3395CB1A19ULL,
+ 0x90C79D3FEDD3F122ULL, 0xD2377CD44439C7B1ULL, 0x15265EE8BE079C04ULL,
+ 0x57D6BF0317EDAA97ULL, 0xD9F4FB7AE3911DFDULL, 0x9B041A914A7B2B6EULL,
+ 0x5C1538ADB04570DBULL, 0x1EE5D94619AF4648ULL, 0x02A151B5F156289CULL,
+ 0x4051B05E58BC1E0FULL, 0x87409262A28245BAULL, 0xC5B073890B687329ULL,
+ 0x4B9237F0FF14C443ULL, 0x0962D61B56FEF2D0ULL, 0xCE73F427ACC0A965ULL,
+ 0x8C8315CC052A9FF6ULL, 0x3A80143F5CF17F13ULL, 0x7870F5D4F51B4980ULL,
+ 0xBF61D7E80F251235ULL, 0xFD913603A6CF24A6ULL, 0x73B3727A52B393CCULL,
+ 0x31439391FB59A55FULL, 0xF652B1AD0167FEEAULL, 0xB4A25046A88DC879ULL,
+ 0xA8E6D8B54074A6ADULL, 0xEA16395EE99E903EULL, 0x2D071B6213A0CB8BULL,
+ 0x6FF7FA89BA4AFD18ULL, 0xE1D5BEF04E364A72ULL, 0xA3255F1BE7DC7CE1ULL,
+ 0x64347D271DE22754ULL, 0x26C49CCCB40811C7ULL, 0x5CBD6CC0CC10FAFCULL,
+ 0x1E4D8D2B65FACC6FULL, 0xD95CAF179FC497DAULL, 0x9BAC4EFC362EA149ULL,
+ 0x158E0A85C2521623ULL, 0x577EEB6E6BB820B0ULL, 0x906FC95291867B05ULL,
+ 0xD29F28B9386C4D96ULL, 0xCEDBA04AD0952342ULL, 0x8C2B41A1797F15D1ULL,
+ 0x4B3A639D83414E64ULL, 0x09CA82762AAB78F7ULL, 0x87E8C60FDED7CF9DULL,
+ 0xC51827E4773DF90EULL, 0x020905D88D03A2BBULL, 0x40F9E43324E99428ULL,
+ 0x2CFFE7D5975E55E2ULL, 0x6E0F063E3EB46371ULL, 0xA91E2402C48A38C4ULL,
+ 0xEBEEC5E96D600E57ULL, 0x65CC8190991CB93DULL, 0x273C607B30F68FAEULL,
+ 0xE02D4247CAC8D41BULL, 0xA2DDA3AC6322E288ULL, 0xBE992B5F8BDB8C5CULL,
+ 0xFC69CAB42231BACFULL, 0x3B78E888D80FE17AULL, 0x7988096371E5D7E9ULL,
+ 0xF7AA4D1A85996083ULL, 0xB55AACF12C735610ULL, 0x724B8ECDD64D0DA5ULL,
+ 0x30BB6F267FA73B36ULL, 0x4AC29F2A07BFD00DULL, 0x08327EC1AE55E69EULL,
+ 0xCF235CFD546BBD2BULL, 0x8DD3BD16FD818BB8ULL, 0x03F1F96F09FD3CD2ULL,
+ 0x41011884A0170A41ULL, 0x86103AB85A2951F4ULL, 0xC4E0DB53F3C36767ULL,
+ 0xD8A453A01B3A09B3ULL, 0x9A54B24BB2D03F20ULL, 0x5D45907748EE6495ULL,
+ 0x1FB5719CE1045206ULL, 0x919735E51578E56CULL, 0xD367D40EBC92D3FFULL,
+ 0x1476F63246AC884AULL, 0x568617D9EF46BED9ULL, 0xE085162AB69D5E3CULL,
+ 0xA275F7C11F7768AFULL, 0x6564D5FDE549331AULL, 0x279434164CA30589ULL,
+ 0xA9B6706FB8DFB2E3ULL, 0xEB46918411358470ULL, 0x2C57B3B8EB0BDFC5ULL,
+ 0x6EA7525342E1E956ULL, 0x72E3DAA0AA188782ULL, 0x30133B4B03F2B111ULL,
+ 0xF7021977F9CCEAA4ULL, 0xB5F2F89C5026DC37ULL, 0x3BD0BCE5A45A6B5DULL,
+ 0x79205D0E0DB05DCEULL, 0xBE317F32F78E067BULL, 0xFCC19ED95E6430E8ULL,
+ 0x86B86ED5267CDBD3ULL, 0xC4488F3E8F96ED40ULL, 0x0359AD0275A8B6F5ULL,
+ 0x41A94CE9DC428066ULL, 0xCF8B0890283E370CULL, 0x8D7BE97B81D4019FULL,
+ 0x4A6ACB477BEA5A2AULL, 0x089A2AACD2006CB9ULL, 0x14DEA25F3AF9026DULL,
+ 0x562E43B4931334FEULL, 0x913F6188692D6F4BULL, 0xD3CF8063C0C759D8ULL,
+ 0x5DEDC41A34BBEEB2ULL, 0x1F1D25F19D51D821ULL, 0xD80C07CD676F8394ULL,
+ 0x9AFCE626CE85B507ULL
+};
+
+void
+isc_crc64_init(uint64_t *crc) {
+ REQUIRE(crc != NULL);
+
+ *crc = 0xffffffffffffffffULL;
+}
+
+void
+isc_crc64_update(uint64_t *crc, const void *data, size_t len) {
+ const unsigned char *p = data;
+ int i;
+
+ REQUIRE(crc != NULL);
+ REQUIRE(data != NULL);
+
+ while (len-- > 0U) {
+ i = ((int)(*crc >> 56) ^ *p++) & 0xff;
+ *crc = crc64_table[i] ^ (*crc << 8);
+ }
+}
+
+void
+isc_crc64_final(uint64_t *crc) {
+ REQUIRE(crc != NULL);
+
+ *crc ^= 0xffffffffffffffffULL;
+}
diff --git a/lib/isc/dir.c b/lib/isc/dir.c
new file mode 100644
index 0000000..b7eabe9
--- /dev/null
+++ b/lib/isc/dir.c
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <isc/dir.h>
+#include <isc/magic.h>
+#include <isc/netdb.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+#define ISC_DIR_MAGIC ISC_MAGIC('D', 'I', 'R', '*')
+#define VALID_DIR(dir) ISC_MAGIC_VALID(dir, ISC_DIR_MAGIC)
+
+void
+isc_dir_init(isc_dir_t *dir) {
+ REQUIRE(dir != NULL);
+
+ dir->entry.name[0] = '\0';
+ dir->entry.length = 0;
+
+ dir->handle = NULL;
+
+ dir->magic = ISC_DIR_MAGIC;
+}
+
+/*!
+ * \brief Allocate workspace and open directory stream. If either one fails,
+ * NULL will be returned.
+ */
+isc_result_t
+isc_dir_open(isc_dir_t *dir, const char *dirname) {
+ char *p;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(VALID_DIR(dir));
+ REQUIRE(dirname != NULL);
+
+ /*
+ * Copy directory name. Need to have enough space for the name,
+ * a possible path separator, the wildcard, and the final NUL.
+ */
+ if (strlen(dirname) + 3 > sizeof(dir->dirname)) {
+ /* XXXDCL ? */
+ return (ISC_R_NOSPACE);
+ }
+ strlcpy(dir->dirname, dirname, sizeof(dir->dirname));
+
+ /*
+ * Append path separator, if needed, and "*".
+ */
+ p = dir->dirname + strlen(dir->dirname);
+ if (dir->dirname < p && *(p - 1) != '/') {
+ *p++ = '/';
+ }
+ *p++ = '*';
+ *p = '\0';
+
+ /*
+ * Open stream.
+ */
+ dir->handle = opendir(dirname);
+
+ if (dir->handle == NULL) {
+ return (isc__errno2result(errno));
+ }
+
+ return (result);
+}
+
+/*!
+ * \brief Return previously retrieved file or get next one.
+ *
+ * Unix's dirent has
+ * separate open and read functions, but the Win32 and DOS interfaces open
+ * the dir stream and reads the first file in one operation.
+ */
+isc_result_t
+isc_dir_read(isc_dir_t *dir) {
+ struct dirent *entry;
+
+ REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
+
+ /*
+ * Fetch next file in directory.
+ */
+ entry = readdir(dir->handle);
+
+ if (entry == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Make sure that the space for the name is long enough.
+ */
+ if (sizeof(dir->entry.name) <= strlen(entry->d_name)) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ strlcpy(dir->entry.name, entry->d_name, sizeof(dir->entry.name));
+
+ /*
+ * Some dirents have d_namlen, but it is not portable.
+ */
+ dir->entry.length = strlen(entry->d_name);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*!
+ * \brief Close directory stream.
+ */
+void
+isc_dir_close(isc_dir_t *dir) {
+ REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
+
+ (void)closedir(dir->handle);
+ dir->handle = NULL;
+}
+
+/*!
+ * \brief Reposition directory stream at start.
+ */
+isc_result_t
+isc_dir_reset(isc_dir_t *dir) {
+ REQUIRE(VALID_DIR(dir) && dir->handle != NULL);
+
+ rewinddir(dir->handle);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_dir_chdir(const char *dirname) {
+ /*!
+ * \brief Change the current directory to 'dirname'.
+ */
+
+ REQUIRE(dirname != NULL);
+
+ if (chdir(dirname) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_dir_chroot(const char *dirname) {
+#ifdef HAVE_CHROOT
+ void *tmp;
+#endif /* ifdef HAVE_CHROOT */
+
+ REQUIRE(dirname != NULL);
+
+#ifdef HAVE_CHROOT
+ /*
+ * Try to use getservbyname and getprotobyname before chroot.
+ * If WKS records are used in a zone under chroot, Name Service Switch
+ * may fail to load library in chroot.
+ * Do not report errors if it fails, we do not need any result now.
+ */
+ tmp = getprotobyname("udp");
+ if (tmp != NULL) {
+ (void)getservbyname("domain", "udp");
+ }
+
+ if (chroot(dirname) < 0 || chdir("/") < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ return (ISC_R_SUCCESS);
+#else /* ifdef HAVE_CHROOT */
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* ifdef HAVE_CHROOT */
+}
+
+isc_result_t
+isc_dir_createunique(char *templet) {
+ isc_result_t result;
+ char *x;
+ char *p;
+ int i;
+ int pid;
+
+ REQUIRE(templet != NULL);
+
+ /*!
+ * \brief mkdtemp is not portable, so this emulates it.
+ */
+
+ pid = getpid();
+
+ /*
+ * Replace trailing Xs with the process-id, zero-filled.
+ */
+ for (x = templet + strlen(templet) - 1; *x == 'X' && x >= templet;
+ x--, pid /= 10)
+ {
+ *x = pid % 10 + '0';
+ }
+
+ x++; /* Set x to start of ex-Xs. */
+
+ do {
+ i = mkdir(templet, 0700);
+ if (i == 0 || errno != EEXIST) {
+ break;
+ }
+
+ /*
+ * The BSD algorithm.
+ */
+ p = x;
+ while (*p != '\0') {
+ if (isdigit((unsigned char)*p)) {
+ *p = 'a';
+ } else if (*p != 'z') {
+ ++*p;
+ } else {
+ /*
+ * Reset character and move to next.
+ */
+ *p++ = 'a';
+ continue;
+ }
+
+ break;
+ }
+
+ if (*p == '\0') {
+ /*
+ * Tried all combinations. errno should already
+ * be EEXIST, but ensure it is anyway for
+ * isc__errno2result().
+ */
+ errno = EEXIST;
+ break;
+ }
+ } while (1);
+
+ if (i == -1) {
+ result = isc__errno2result(errno);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ return (result);
+}
diff --git a/lib/isc/entropy.c b/lib/isc/entropy.c
new file mode 100644
index 0000000..38fcfa0
--- /dev/null
+++ b/lib/isc/entropy.c
@@ -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.
+ */
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include "entropy_private.h"
+
+void
+isc_entropy_get(void *buf, size_t buflen) {
+ if (RAND_bytes(buf, buflen) < 1) {
+ FATAL_ERROR("RAND_bytes(): %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ }
+}
diff --git a/lib/isc/entropy_private.h b/lib/isc/entropy_private.h
new file mode 100644
index 0000000..df9a382
--- /dev/null
+++ b/lib/isc/entropy_private.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
+
+#include <stdlib.h>
+
+#include <isc/lang.h>
+
+/*! \file isc/entropy_private.h
+ * \brief Implements wrapper around CSPRNG cryptographic library calls
+ * for getting cryptographically secure pseudo-random numbers.
+ *
+ * - If OpenSSL is used, it uses RAND_bytes()
+ * - If PKCS#11 is used, it uses pkcs_C_GenerateRandom()
+ *
+ */
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_entropy_get(void *buf, size_t buflen);
+/*!<
+ * \brief Get cryptographically-secure pseudo-random data.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/errno.c b/lib/isc/errno.c
new file mode 100644
index 0000000..5875f3b
--- /dev/null
+++ b/lib/isc/errno.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <isc/errno.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+isc_result_t
+isc_errno_toresult(int err) {
+ return (isc___errno2result(err, false, 0, 0));
+}
diff --git a/lib/isc/errno2result.c b/lib/isc/errno2result.c
new file mode 100644
index 0000000..1ef0c88
--- /dev/null
+++ b/lib/isc/errno2result.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include "errno2result.h"
+#include <stdbool.h>
+
+#include <isc/result.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+/*%
+ * Convert a POSIX errno value into an isc_result_t. The
+ * list of supported errno values is not complete; new users
+ * of this function should add any expected errors that are
+ * not already there.
+ */
+isc_result_t
+isc___errno2result(int posixerrno, bool dolog, const char *file,
+ unsigned int line) {
+ char strbuf[ISC_STRERRORSIZE];
+
+ switch (posixerrno) {
+ case ENOTDIR:
+ case ELOOP:
+ case EINVAL: /* XXX sometimes this is not for files */
+ case ENAMETOOLONG:
+ case EBADF:
+ return (ISC_R_INVALIDFILE);
+ case EISDIR:
+ return (ISC_R_NOTFILE);
+ case ENOENT:
+ return (ISC_R_FILENOTFOUND);
+ case EACCES:
+ case EPERM:
+ case EROFS:
+ return (ISC_R_NOPERM);
+ case EEXIST:
+ return (ISC_R_FILEEXISTS);
+ case EIO:
+ return (ISC_R_IOERROR);
+ case ENOMEM:
+ return (ISC_R_NOMEMORY);
+ case ENFILE:
+ case EMFILE:
+ return (ISC_R_TOOMANYOPENFILES);
+#ifdef EDQUOT
+ case EDQUOT:
+ return (ISC_R_DISCQUOTA);
+#endif /* ifdef EDQUOT */
+ case ENOSPC:
+ return (ISC_R_DISCFULL);
+#ifdef EOVERFLOW
+ case EOVERFLOW:
+ return (ISC_R_RANGE);
+#endif /* ifdef EOVERFLOW */
+ case EPIPE:
+#ifdef ECONNRESET
+ case ECONNRESET:
+#endif /* ifdef ECONNRESET */
+#ifdef ECONNABORTED
+ case ECONNABORTED:
+#endif /* ifdef ECONNABORTED */
+ return (ISC_R_CONNECTIONRESET);
+#ifdef ENOTCONN
+ case ENOTCONN:
+ return (ISC_R_NOTCONNECTED);
+#endif /* ifdef ENOTCONN */
+#ifdef ETIMEDOUT
+ case ETIMEDOUT:
+ return (ISC_R_TIMEDOUT);
+#endif /* ifdef ETIMEDOUT */
+#ifdef ENOBUFS
+ case ENOBUFS:
+ return (ISC_R_NORESOURCES);
+#endif /* ifdef ENOBUFS */
+#ifdef EAFNOSUPPORT
+ case EAFNOSUPPORT:
+ return (ISC_R_FAMILYNOSUPPORT);
+#endif /* ifdef EAFNOSUPPORT */
+#ifdef ENETDOWN
+ case ENETDOWN:
+ return (ISC_R_NETDOWN);
+#endif /* ifdef ENETDOWN */
+#ifdef EHOSTDOWN
+ case EHOSTDOWN:
+ return (ISC_R_HOSTDOWN);
+#endif /* ifdef EHOSTDOWN */
+#ifdef ENETUNREACH
+ case ENETUNREACH:
+ return (ISC_R_NETUNREACH);
+#endif /* ifdef ENETUNREACH */
+#ifdef EHOSTUNREACH
+ case EHOSTUNREACH:
+ return (ISC_R_HOSTUNREACH);
+#endif /* ifdef EHOSTUNREACH */
+#ifdef EADDRINUSE
+ case EADDRINUSE:
+ return (ISC_R_ADDRINUSE);
+#endif /* ifdef EADDRINUSE */
+ case EADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case ECONNREFUSED:
+ return (ISC_R_CONNREFUSED);
+ default:
+ if (dolog) {
+ strerror_r(posixerrno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR(file, line,
+ "unable to convert errno "
+ "to isc_result: %d: %s",
+ posixerrno, strbuf);
+ }
+ /*
+ * XXXDCL would be nice if perhaps this function could
+ * return the system's error string, so the caller
+ * might have something more descriptive than "unexpected
+ * error" to log with.
+ */
+ return (ISC_R_UNEXPECTED);
+ }
+}
diff --git a/lib/isc/errno2result.h b/lib/isc/errno2result.h
new file mode 100644
index 0000000..80fee69
--- /dev/null
+++ b/lib/isc/errno2result.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
+
+/*! \file */
+
+/* XXXDCL this should be moved to lib/isc/include/isc/errno2result.h. */
+
+#include <errno.h> /* Provides errno. */
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define isc__errno2result(x) isc___errno2result(x, true, __FILE__, __LINE__)
+
+isc_result_t
+isc___errno2result(int posixerrno, bool dolog, const char *file,
+ unsigned int line);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/error.c b/lib/isc/error.c
new file mode 100644
index 0000000..051a6c4
--- /dev/null
+++ b/lib/isc/error.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <isc/error.h>
+#include <isc/print.h>
+
+/*% Default unexpected callback. */
+static void
+default_unexpected_callback(const char *, int, const char *, const char *,
+ va_list) ISC_FORMAT_PRINTF(4, 0);
+
+/*% Default fatal callback. */
+static void
+default_fatal_callback(const char *, int, const char *, const char *, va_list)
+ ISC_FORMAT_PRINTF(3, 0);
+
+/*% unexpected_callback */
+static isc_errorcallback_t unexpected_callback = default_unexpected_callback;
+static isc_errorcallback_t fatal_callback = default_fatal_callback;
+
+void
+isc_error_setunexpected(isc_errorcallback_t cb) {
+ if (cb == NULL) {
+ unexpected_callback = default_unexpected_callback;
+ } else {
+ unexpected_callback = cb;
+ }
+}
+
+void
+isc_error_setfatal(isc_errorcallback_t cb) {
+ if (cb == NULL) {
+ fatal_callback = default_fatal_callback;
+ } else {
+ fatal_callback = cb;
+ }
+}
+
+void
+isc_error_unexpected(const char *file, int line, const char *func,
+ const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (unexpected_callback)(file, line, func, format, args);
+ va_end(args);
+}
+
+void
+isc_error_fatal(const char *file, int line, const char *func,
+ const char *format, ...) {
+ va_list args;
+
+ va_start(args, format);
+ (fatal_callback)(file, line, func, format, args);
+ va_end(args);
+ abort();
+}
+
+static void
+default_unexpected_callback(const char *file, int line, const char *func,
+ const char *format, va_list args) {
+ fprintf(stderr, "%s:%d:%s(): ", file, line, func);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
+
+static void
+default_fatal_callback(const char *file, int line, const char *func,
+ const char *format, va_list args) {
+ fprintf(stderr, "%s:%d:%s(): fatal error: ", file, line, func);
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ fflush(stderr);
+}
diff --git a/lib/isc/event.c b/lib/isc/event.c
new file mode 100644
index 0000000..4849d01
--- /dev/null
+++ b/lib/isc/event.c
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*!
+ * \file
+ */
+
+#include <isc/event.h>
+#include <isc/mem.h>
+#include <isc/util.h>
+
+/***
+ *** Events.
+ ***/
+
+static void
+destroy(isc_event_t *event) {
+ isc_mem_t *mctx = event->ev_destroy_arg;
+
+ isc_mem_put(mctx, event, event->ev_size);
+}
+
+isc_event_t *
+isc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
+ isc_taskaction_t action, void *arg, size_t size) {
+ isc_event_t *event;
+
+ REQUIRE(size >= sizeof(struct isc_event));
+ REQUIRE(action != NULL);
+
+ event = isc_mem_get(mctx, size);
+
+ ISC_EVENT_INIT(event, size, 0, NULL, type, action, arg, sender, destroy,
+ mctx);
+
+ return (event);
+}
+
+isc_event_t *
+isc_event_constallocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
+ isc_taskaction_t action, const void *arg, size_t size) {
+ isc_event_t *event;
+ void *deconst_arg;
+
+ REQUIRE(size >= sizeof(struct isc_event));
+ REQUIRE(action != NULL);
+
+ event = isc_mem_get(mctx, size);
+
+ /*
+ * Removing the const attribute from "arg" is the best of two
+ * evils here. If the event->ev_arg member is made const, then
+ * it affects a great many users of the task/event subsystem
+ * which are not passing in an "arg" which starts its life as
+ * const. Changing isc_event_allocate() and isc_task_onshutdown()
+ * to not have "arg" prototyped as const (which is quite legitimate,
+ * because neither of those functions modify arg) can cause
+ * compiler whining anytime someone does want to use a const
+ * arg that they themselves never modify, such as with
+ * gcc -Wwrite-strings and using a string "arg".
+ */
+ DE_CONST(arg, deconst_arg);
+
+ ISC_EVENT_INIT(event, size, 0, NULL, type, action, deconst_arg, sender,
+ destroy, mctx);
+
+ return (event);
+}
+
+void
+isc_event_free(isc_event_t **eventp) {
+ isc_event_t *event;
+
+ REQUIRE(eventp != NULL);
+ event = *eventp;
+ *eventp = NULL;
+ REQUIRE(event != NULL);
+
+ REQUIRE(!ISC_LINK_LINKED(event, ev_link));
+ REQUIRE(!ISC_LINK_LINKED(event, ev_ratelink));
+
+ if (event->ev_destroy != NULL) {
+ (event->ev_destroy)(event);
+ }
+}
diff --git a/lib/isc/file.c b/lib/isc/file.c
new file mode 100644
index 0000000..e3a61de
--- /dev/null
+++ b/lib/isc/file.c
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <time.h> /* Required for utimes on some platforms. */
+#include <unistd.h> /* Required for mkstemp on NetBSD. */
+
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif /* ifdef HAVE_SYS_MMAN_H */
+
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/log.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+/*
+ * XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
+ * it might be good to provide a mechanism that allows for the results
+ * of a previous stat() to be used again without having to do another stat,
+ * such as perl's mechanism of using "_" in place of a file name to indicate
+ * that the results of the last stat should be used. But then you get into
+ * annoying MP issues. BTW, Win32 has stat().
+ */
+static isc_result_t
+file_stats(const char *file, struct stat *stats) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(stats != NULL);
+
+ if (stat(file, stats) != 0) {
+ result = isc__errno2result(errno);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+fd_stats(int fd, struct stat *stats) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(stats != NULL);
+
+ if (fstat(fd, stats) != 0) {
+ result = isc__errno2result(errno);
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_getsizefd(int fd, off_t *size) {
+ isc_result_t result;
+ struct stat stats;
+
+ REQUIRE(size != NULL);
+
+ result = fd_stats(fd, &stats);
+
+ if (result == ISC_R_SUCCESS) {
+ *size = stats.st_size;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_mode(const char *file, mode_t *modep) {
+ isc_result_t result;
+ struct stat stats;
+
+ REQUIRE(modep != NULL);
+
+ result = file_stats(file, &stats);
+ if (result == ISC_R_SUCCESS) {
+ *modep = (stats.st_mode & 07777);
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_getmodtime(const char *file, isc_time_t *modtime) {
+ isc_result_t result;
+ struct stat stats;
+
+ REQUIRE(file != NULL);
+ REQUIRE(modtime != NULL);
+
+ result = file_stats(file, &stats);
+
+ if (result == ISC_R_SUCCESS) {
+#if defined(HAVE_STAT_NSEC)
+ isc_time_set(modtime, stats.st_mtime, stats.st_mtim.tv_nsec);
+#else /* if defined(HAVE_STAT_NSEC) */
+ isc_time_set(modtime, stats.st_mtime, 0);
+#endif /* if defined(HAVE_STAT_NSEC) */
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_getsize(const char *file, off_t *size) {
+ isc_result_t result;
+ struct stat stats;
+
+ REQUIRE(file != NULL);
+ REQUIRE(size != NULL);
+
+ result = file_stats(file, &stats);
+
+ if (result == ISC_R_SUCCESS) {
+ *size = stats.st_size;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_settime(const char *file, isc_time_t *when) {
+ struct timeval times[2];
+
+ REQUIRE(file != NULL && when != NULL);
+
+ /*
+ * tv_sec is at least a 32 bit quantity on all platforms we're
+ * dealing with, but it is signed on most (all?) of them,
+ * so we need to make sure the high bit isn't set. This unfortunately
+ * loses when either:
+ * * tv_sec becomes a signed 64 bit integer but long is 32 bits
+ * and isc_time_seconds > LONG_MAX, or
+ * * isc_time_seconds is changed to be > 32 bits but long is 32 bits
+ * and isc_time_seconds has at least 33 significant bits.
+ */
+ times[0].tv_sec = times[1].tv_sec = (long)isc_time_seconds(when);
+
+ /*
+ * Here is the real check for the high bit being set.
+ */
+ if ((times[0].tv_sec &
+ (1ULL << (sizeof(times[0].tv_sec) * CHAR_BIT - 1))) != 0)
+ {
+ return (ISC_R_RANGE);
+ }
+
+ /*
+ * isc_time_nanoseconds guarantees a value that divided by 1000 will
+ * fit into the minimum possible size tv_usec field.
+ */
+ times[0].tv_usec = times[1].tv_usec =
+ (int32_t)(isc_time_nanoseconds(when) / 1000);
+
+ if (utimes(file, times) < 0) {
+ return (isc__errno2result(errno));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+#undef TEMPLATE
+#define TEMPLATE "tmp-XXXXXXXXXX" /*%< 14 characters. */
+
+isc_result_t
+isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
+ return (isc_file_template(path, TEMPLATE, buf, buflen));
+}
+
+isc_result_t
+isc_file_template(const char *path, const char *templet, char *buf,
+ size_t buflen) {
+ const char *s;
+
+ REQUIRE(templet != NULL);
+ REQUIRE(buf != NULL);
+
+ if (path == NULL) {
+ path = "";
+ }
+
+ s = strrchr(templet, '/');
+ if (s != NULL) {
+ templet = s + 1;
+ }
+
+ s = strrchr(path, '/');
+
+ if (s != NULL) {
+ size_t prefixlen = s - path + 1;
+ if ((prefixlen + strlen(templet) + 1) > buflen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /* Copy 'prefixlen' bytes and NUL terminate. */
+ strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen));
+ strlcat(buf, templet, buflen);
+ } else {
+ if ((strlen(templet) + 1) > buflen) {
+ return (ISC_R_NOSPACE);
+ }
+
+ strlcpy(buf, templet, buflen);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static const char alphnum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"
+ "wxyz0123456789";
+
+isc_result_t
+isc_file_renameunique(const char *file, char *templet) {
+ char *x;
+ char *cp;
+
+ REQUIRE(file != NULL);
+ REQUIRE(templet != NULL);
+
+ cp = templet;
+ while (*cp != '\0') {
+ cp++;
+ }
+ if (cp == templet) {
+ return (ISC_R_FAILURE);
+ }
+
+ x = cp--;
+ while (cp >= templet && *cp == 'X') {
+ *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
+ x = cp--;
+ }
+ while (link(file, templet) == -1) {
+ if (errno != EEXIST) {
+ return (isc__errno2result(errno));
+ }
+ for (cp = x;;) {
+ const char *t;
+ if (*cp == '\0') {
+ return (ISC_R_FAILURE);
+ }
+ t = strchr(alphnum, *cp);
+ if (t == NULL || *++t == '\0') {
+ *cp++ = alphnum[0];
+ } else {
+ *cp = *t;
+ break;
+ }
+ }
+ }
+ if (unlink(file) < 0) {
+ if (errno != ENOENT) {
+ return (isc__errno2result(errno));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_openunique(char *templet, FILE **fp) {
+ int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ return (isc_file_openuniquemode(templet, mode, fp));
+}
+
+isc_result_t
+isc_file_openuniqueprivate(char *templet, FILE **fp) {
+ int mode = S_IWUSR | S_IRUSR;
+ return (isc_file_openuniquemode(templet, mode, fp));
+}
+
+isc_result_t
+isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
+ int fd;
+ FILE *f;
+ isc_result_t result = ISC_R_SUCCESS;
+ char *x;
+ char *cp;
+
+ REQUIRE(templet != NULL);
+ REQUIRE(fp != NULL && *fp == NULL);
+
+ cp = templet;
+ while (*cp != '\0') {
+ cp++;
+ }
+ if (cp == templet) {
+ return (ISC_R_FAILURE);
+ }
+
+ x = cp--;
+ while (cp >= templet && *cp == 'X') {
+ *cp = alphnum[isc_random_uniform(sizeof(alphnum) - 1)];
+ x = cp--;
+ }
+
+ while ((fd = open(templet, O_RDWR | O_CREAT | O_EXCL, mode)) == -1) {
+ if (errno != EEXIST) {
+ return (isc__errno2result(errno));
+ }
+ for (cp = x;;) {
+ char *t;
+ if (*cp == '\0') {
+ return (ISC_R_FAILURE);
+ }
+ t = strchr(alphnum, *cp);
+ if (t == NULL || *++t == '\0') {
+ *cp++ = alphnum[0];
+ } else {
+ *cp = *t;
+ break;
+ }
+ }
+ }
+ f = fdopen(fd, "w+");
+ if (f == NULL) {
+ result = isc__errno2result(errno);
+ if (remove(templet) < 0) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_FILE, ISC_LOG_ERROR,
+ "remove '%s': failed", templet);
+ }
+ (void)close(fd);
+ } else {
+ *fp = f;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_bopenunique(char *templet, FILE **fp) {
+ int mode = S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ return (isc_file_openuniquemode(templet, mode, fp));
+}
+
+isc_result_t
+isc_file_bopenuniqueprivate(char *templet, FILE **fp) {
+ int mode = S_IWUSR | S_IRUSR;
+ return (isc_file_openuniquemode(templet, mode, fp));
+}
+
+isc_result_t
+isc_file_bopenuniquemode(char *templet, int mode, FILE **fp) {
+ return (isc_file_openuniquemode(templet, mode, fp));
+}
+
+isc_result_t
+isc_file_remove(const char *filename) {
+ int r;
+
+ REQUIRE(filename != NULL);
+
+ r = unlink(filename);
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_file_rename(const char *oldname, const char *newname) {
+ int r;
+
+ REQUIRE(oldname != NULL);
+ REQUIRE(newname != NULL);
+
+ r = rename(oldname, newname);
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+bool
+isc_file_exists(const char *pathname) {
+ struct stat stats;
+
+ REQUIRE(pathname != NULL);
+
+ return (file_stats(pathname, &stats) == ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_isplainfile(const char *filename) {
+ /*
+ * This function returns success if filename is a plain file.
+ */
+ struct stat filestat;
+ memset(&filestat, 0, sizeof(struct stat));
+
+ if ((stat(filename, &filestat)) == -1) {
+ return (isc__errno2result(errno));
+ }
+
+ if (!S_ISREG(filestat.st_mode)) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_isplainfilefd(int fd) {
+ /*
+ * This function returns success if filename is a plain file.
+ */
+ struct stat filestat;
+ memset(&filestat, 0, sizeof(struct stat));
+
+ if ((fstat(fd, &filestat)) == -1) {
+ return (isc__errno2result(errno));
+ }
+
+ if (!S_ISREG(filestat.st_mode)) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_isdirectory(const char *filename) {
+ /*
+ * This function returns success if filename exists and is a
+ * directory.
+ */
+ struct stat filestat;
+ memset(&filestat, 0, sizeof(struct stat));
+
+ if ((stat(filename, &filestat)) == -1) {
+ return (isc__errno2result(errno));
+ }
+
+ if (!S_ISDIR(filestat.st_mode)) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+bool
+isc_file_isabsolute(const char *filename) {
+ REQUIRE(filename != NULL);
+ return (filename[0] == '/');
+}
+
+bool
+isc_file_iscurrentdir(const char *filename) {
+ REQUIRE(filename != NULL);
+ return (filename[0] == '.' && filename[1] == '\0');
+}
+
+bool
+isc_file_ischdiridempotent(const char *filename) {
+ REQUIRE(filename != NULL);
+ if (isc_file_isabsolute(filename)) {
+ return (true);
+ }
+ if (isc_file_iscurrentdir(filename)) {
+ return (true);
+ }
+ return (false);
+}
+
+const char *
+isc_file_basename(const char *filename) {
+ const char *s;
+
+ REQUIRE(filename != NULL);
+
+ s = strrchr(filename, '/');
+ if (s == NULL) {
+ return (filename);
+ }
+
+ return (s + 1);
+}
+
+isc_result_t
+isc_file_progname(const char *filename, char *buf, size_t buflen) {
+ const char *base;
+ size_t len;
+
+ REQUIRE(filename != NULL);
+ REQUIRE(buf != NULL);
+
+ base = isc_file_basename(filename);
+ len = strlen(base) + 1;
+
+ if (len > buflen) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(buf, base, len);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Put the absolute name of the current directory into 'dirname', which is
+ * a buffer of at least 'length' characters. End the string with the
+ * appropriate path separator, such that the final product could be
+ * concatenated with a relative pathname to make a valid pathname string.
+ */
+static isc_result_t
+dir_current(char *dirname, size_t length) {
+ char *cwd;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(dirname != NULL);
+ REQUIRE(length > 0U);
+
+ cwd = getcwd(dirname, length);
+
+ if (cwd == NULL) {
+ if (errno == ERANGE) {
+ result = ISC_R_NOSPACE;
+ } else {
+ result = isc__errno2result(errno);
+ }
+ } else {
+ if (strlen(dirname) + 1 == length) {
+ result = ISC_R_NOSPACE;
+ } else if (dirname[1] != '\0') {
+ strlcat(dirname, "/", length);
+ }
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
+ isc_result_t result;
+ result = dir_current(path, pathlen);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (strlen(path) + strlen(filename) + 1 > pathlen) {
+ return (ISC_R_NOSPACE);
+ }
+ strlcat(path, filename, pathlen);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_truncate(const char *filename, isc_offset_t size) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ if (truncate(filename, size) < 0) {
+ result = isc__errno2result(errno);
+ }
+ return (result);
+}
+
+isc_result_t
+isc_file_safecreate(const char *filename, FILE **fp) {
+ isc_result_t result;
+ int flags;
+ struct stat sb;
+ FILE *f;
+ int fd;
+
+ REQUIRE(filename != NULL);
+ REQUIRE(fp != NULL && *fp == NULL);
+
+ result = file_stats(filename, &sb);
+ if (result == ISC_R_SUCCESS) {
+ if ((sb.st_mode & S_IFREG) == 0) {
+ return (ISC_R_INVALIDFILE);
+ }
+ flags = O_WRONLY | O_TRUNC;
+ } else if (result == ISC_R_FILENOTFOUND) {
+ flags = O_WRONLY | O_CREAT | O_EXCL;
+ } else {
+ return (result);
+ }
+
+ fd = open(filename, flags, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ return (isc__errno2result(errno));
+ }
+
+ f = fdopen(fd, "w");
+ if (f == NULL) {
+ result = isc__errno2result(errno);
+ close(fd);
+ return (result);
+ }
+
+ *fp = f;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
+ char const **bname) {
+ char *dir;
+ const char *file, *slash;
+
+ if (path == NULL) {
+ return (ISC_R_INVALIDFILE);
+ }
+
+ slash = strrchr(path, '/');
+
+ if (slash == path) {
+ file = ++slash;
+ dir = isc_mem_strdup(mctx, "/");
+ } else if (slash != NULL) {
+ file = ++slash;
+ dir = isc_mem_allocate(mctx, slash - path);
+ strlcpy(dir, path, slash - path);
+ } else {
+ file = path;
+ dir = isc_mem_strdup(mctx, ".");
+ }
+
+ if (dir == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (*file == '\0') {
+ isc_mem_free(mctx, dir);
+ return (ISC_R_INVALIDFILE);
+ }
+
+ *dirname = dir;
+ *bname = file;
+
+ return (ISC_R_SUCCESS);
+}
+
+#define DISALLOW "\\/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+static isc_result_t
+digest2hex(unsigned char *digest, unsigned int digestlen, char *hash,
+ size_t hashlen) {
+ unsigned int i;
+ int ret;
+ for (i = 0; i < digestlen; i++) {
+ size_t left = hashlen - i * 2;
+ ret = snprintf(hash + i * 2, left, "%02x", digest[i]);
+ if (ret < 0 || (size_t)ret >= left) {
+ return (ISC_R_NOSPACE);
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_file_sanitize(const char *dir, const char *base, const char *ext,
+ char *path, size_t length) {
+ char buf[PATH_MAX];
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen;
+ char hash[ISC_MAX_MD_SIZE * 2 + 1];
+ size_t l = 0;
+ isc_result_t err;
+
+ REQUIRE(base != NULL);
+ REQUIRE(path != NULL);
+
+ l = strlen(base) + 1;
+
+ /*
+ * allow room for a full sha256 hash (64 chars
+ * plus null terminator)
+ */
+ if (l < 65U) {
+ l = 65;
+ }
+
+ if (dir != NULL) {
+ l += strlen(dir) + 1;
+ }
+ if (ext != NULL) {
+ l += strlen(ext) + 1;
+ }
+
+ if (l > length || l > (unsigned)PATH_MAX) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /* Check whether the full-length SHA256 hash filename exists */
+ err = isc_md(ISC_MD_SHA256, (const unsigned char *)base, strlen(base),
+ digest, &digestlen);
+ if (err != ISC_R_SUCCESS) {
+ return (err);
+ }
+
+ err = digest2hex(digest, digestlen, hash, sizeof(hash));
+ if (err != ISC_R_SUCCESS) {
+ return (err);
+ }
+
+ snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
+ dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
+ ext != NULL ? ext : "");
+ if (isc_file_exists(buf)) {
+ strlcpy(path, buf, length);
+ return (ISC_R_SUCCESS);
+ }
+
+ /* Check for a truncated SHA256 hash filename */
+ hash[16] = '\0';
+ snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
+ dir != NULL ? "/" : "", hash, ext != NULL ? "." : "",
+ ext != NULL ? ext : "");
+ if (isc_file_exists(buf)) {
+ strlcpy(path, buf, length);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If neither hash filename already exists, then we'll use
+ * the original base name if it has no disallowed characters,
+ * or the truncated hash name if it does.
+ */
+ if (strpbrk(base, DISALLOW) != NULL) {
+ strlcpy(path, buf, length);
+ return (ISC_R_SUCCESS);
+ }
+
+ snprintf(buf, sizeof(buf), "%s%s%s%s%s", dir != NULL ? dir : "",
+ dir != NULL ? "/" : "", base, ext != NULL ? "." : "",
+ ext != NULL ? ext : "");
+ strlcpy(path, buf, length);
+ return (ISC_R_SUCCESS);
+}
+
+bool
+isc_file_isdirwritable(const char *path) {
+ return (access(path, W_OK | X_OK) == 0);
+}
diff --git a/lib/isc/glob.c b/lib/isc/glob.c
new file mode 100644
index 0000000..66427b3
--- /dev/null
+++ b/lib/isc/glob.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <errno.h>
+#include <glob.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <isc/errno.h>
+#include <isc/glob.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+isc_result_t
+isc_glob(const char *pattern, glob_t *pglob) {
+ REQUIRE(pattern != NULL);
+ REQUIRE(*pattern != '\0');
+ REQUIRE(pglob != NULL);
+
+ int rc = glob(pattern, GLOB_ERR, NULL, pglob);
+
+ switch (rc) {
+ case 0:
+ return (ISC_R_SUCCESS);
+
+ case GLOB_NOMATCH:
+ return (ISC_R_FILENOTFOUND);
+
+ case GLOB_NOSPACE:
+ return (ISC_R_NOMEMORY);
+
+ default:
+ return (errno != 0 ? isc_errno_toresult(errno) : ISC_R_IOERROR);
+ }
+}
+
+void
+isc_globfree(glob_t *pglob) {
+ REQUIRE(pglob != NULL);
+ globfree(pglob);
+}
diff --git a/lib/isc/hash.c b/lib/isc/hash.c
new file mode 100644
index 0000000..522e140
--- /dev/null
+++ b/lib/isc/hash.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 <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "entropy_private.h"
+#include "isc/hash.h" /* IWYU pragma: keep */
+#include "isc/once.h"
+#include "isc/random.h"
+#include "isc/result.h"
+#include "isc/siphash.h"
+#include "isc/string.h"
+#include "isc/types.h"
+#include "isc/util.h"
+
+static uint8_t isc_hash_key[16];
+static uint8_t isc_hash32_key[8];
+static bool hash_initialized = false;
+static isc_once_t isc_hash_once = ISC_ONCE_INIT;
+
+static void
+isc_hash_initialize(void) {
+ /*
+ * Set a constant key to help in problem reproduction should
+ * fuzzing find a crash or a hang.
+ */
+ uint64_t key[2] = { 0, 1 };
+#if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ isc_entropy_get(key, sizeof(key));
+#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+ memmove(isc_hash_key, key, sizeof(isc_hash_key));
+#if !FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ isc_entropy_get(key, sizeof(key));
+#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+ memmove(isc_hash32_key, key, sizeof(isc_hash32_key));
+ hash_initialized = true;
+}
+
+static uint8_t 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
+};
+
+const void *
+isc_hash_get_initializer(void) {
+ if (!hash_initialized) {
+ RUNTIME_CHECK(
+ isc_once_do(&isc_hash_once, isc_hash_initialize) ==
+ ISC_R_SUCCESS);
+ }
+
+ return (isc_hash_key);
+}
+
+void
+isc_hash_set_initializer(const void *initializer) {
+ REQUIRE(initializer != NULL);
+
+ /*
+ * Ensure that isc_hash_initialize() is not called after
+ * isc_hash_set_initializer() is called.
+ */
+ if (!hash_initialized) {
+ RUNTIME_CHECK(
+ isc_once_do(&isc_hash_once, isc_hash_initialize) ==
+ ISC_R_SUCCESS);
+ }
+
+ memmove(isc_hash_key, initializer, sizeof(isc_hash_key));
+}
+
+uint64_t
+isc_hash64(const void *data, const size_t length, const bool case_sensitive) {
+ uint64_t hval;
+
+ REQUIRE(length == 0 || data != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&isc_hash_once, isc_hash_initialize) ==
+ ISC_R_SUCCESS);
+
+ if (case_sensitive) {
+ isc_siphash24(isc_hash_key, data, length, (uint8_t *)&hval);
+ } else {
+ uint8_t input[1024];
+ REQUIRE(length <= 1024);
+ for (unsigned int i = 0; i < length; i++) {
+ input[i] = maptolower[((const uint8_t *)data)[i]];
+ }
+ isc_siphash24(isc_hash_key, input, length, (uint8_t *)&hval);
+ }
+
+ return (hval);
+}
+
+uint32_t
+isc_hash32(const void *data, const size_t length, const bool case_sensitive) {
+ uint32_t hval;
+
+ REQUIRE(length == 0 || data != NULL);
+
+ RUNTIME_CHECK(isc_once_do(&isc_hash_once, isc_hash_initialize) ==
+ ISC_R_SUCCESS);
+
+ if (case_sensitive) {
+ isc_halfsiphash24(isc_hash_key, data, length, (uint8_t *)&hval);
+ } else {
+ uint8_t input[1024];
+ REQUIRE(length <= 1024);
+ for (unsigned int i = 0; i < length; i++) {
+ input[i] = maptolower[((const uint8_t *)data)[i]];
+ }
+ isc_halfsiphash24(isc_hash_key, input, length,
+ (uint8_t *)&hval);
+ }
+
+ return (hval);
+}
diff --git a/lib/isc/heap.c b/lib/isc/heap.c
new file mode 100644
index 0000000..7b0cc28
--- /dev/null
+++ b/lib/isc/heap.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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
+ * Heap implementation of priority queues adapted from the following:
+ *
+ * \li "Introduction to Algorithms," Cormen, Leiserson, and Rivest,
+ * MIT Press / McGraw Hill, 1990, ISBN 0-262-03141-8, chapter 7.
+ *
+ * \li "Algorithms," Second Edition, Sedgewick, Addison-Wesley, 1988,
+ * ISBN 0-201-06673-4, chapter 11.
+ */
+
+#include <stdbool.h>
+
+#include <isc/heap.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/string.h> /* Required for memmove. */
+#include <isc/util.h>
+
+/*@{*/
+/*%
+ * Note: to make heap_parent and heap_left easy to compute, the first
+ * element of the heap array is not used; i.e. heap subscripts are 1-based,
+ * not 0-based. The parent is index/2, and the left-child is index*2.
+ * The right child is index*2+1.
+ */
+#define heap_parent(i) ((i) >> 1)
+#define heap_left(i) ((i) << 1)
+/*@}*/
+
+#define SIZE_INCREMENT 1024
+
+#define HEAP_MAGIC ISC_MAGIC('H', 'E', 'A', 'P')
+#define VALID_HEAP(h) ISC_MAGIC_VALID(h, HEAP_MAGIC)
+
+/*%
+ * When the heap is in a consistent state, the following invariant
+ * holds true: for every element i > 1, heap_parent(i) has a priority
+ * higher than or equal to that of i.
+ */
+#define HEAPCONDITION(i) \
+ ((i) == 1 || \
+ !heap->compare(heap->array[(i)], heap->array[heap_parent(i)]))
+
+/*% ISC heap structure. */
+struct isc_heap {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ unsigned int size;
+ unsigned int size_increment;
+ unsigned int last;
+ void **array;
+ isc_heapcompare_t compare;
+ isc_heapindex_t index;
+};
+
+#ifdef ISC_HEAP_CHECK
+static void
+heap_check(isc_heap_t *heap) {
+ unsigned int i;
+ for (i = 1; i <= heap->last; i++) {
+ INSIST(HEAPCONDITION(i));
+ }
+}
+#else /* ifdef ISC_HEAP_CHECK */
+#define heap_check(x) (void)0
+#endif /* ifdef ISC_HEAP_CHECK */
+
+void
+isc_heap_create(isc_mem_t *mctx, isc_heapcompare_t compare, isc_heapindex_t idx,
+ unsigned int size_increment, isc_heap_t **heapp) {
+ isc_heap_t *heap;
+
+ REQUIRE(heapp != NULL && *heapp == NULL);
+ REQUIRE(compare != NULL);
+
+ heap = isc_mem_get(mctx, sizeof(*heap));
+ heap->magic = HEAP_MAGIC;
+ heap->size = 0;
+ heap->mctx = NULL;
+ isc_mem_attach(mctx, &heap->mctx);
+ if (size_increment == 0) {
+ heap->size_increment = SIZE_INCREMENT;
+ } else {
+ heap->size_increment = size_increment;
+ }
+ heap->last = 0;
+ heap->array = NULL;
+ heap->compare = compare;
+ heap->index = idx;
+
+ *heapp = heap;
+}
+
+void
+isc_heap_destroy(isc_heap_t **heapp) {
+ isc_heap_t *heap;
+
+ REQUIRE(heapp != NULL);
+ heap = *heapp;
+ *heapp = NULL;
+ REQUIRE(VALID_HEAP(heap));
+
+ if (heap->array != NULL) {
+ isc_mem_put(heap->mctx, heap->array,
+ heap->size * sizeof(void *));
+ }
+ heap->magic = 0;
+ isc_mem_putanddetach(&heap->mctx, heap, sizeof(*heap));
+}
+
+static void
+resize(isc_heap_t *heap) {
+ void **new_array;
+ unsigned int new_size;
+
+ REQUIRE(VALID_HEAP(heap));
+
+ new_size = heap->size + heap->size_increment;
+ new_array = isc_mem_get(heap->mctx, new_size * sizeof(void *));
+ if (heap->array != NULL) {
+ memmove(new_array, heap->array, heap->size * sizeof(void *));
+ isc_mem_put(heap->mctx, heap->array,
+ heap->size * sizeof(void *));
+ }
+ heap->size = new_size;
+ heap->array = new_array;
+}
+
+static void
+float_up(isc_heap_t *heap, unsigned int i, void *elt) {
+ unsigned int p;
+
+ for (p = heap_parent(i); i > 1 && heap->compare(elt, heap->array[p]);
+ i = p, p = heap_parent(i))
+ {
+ heap->array[i] = heap->array[p];
+ if (heap->index != NULL) {
+ (heap->index)(heap->array[i], i);
+ }
+ }
+ heap->array[i] = elt;
+ if (heap->index != NULL) {
+ (heap->index)(heap->array[i], i);
+ }
+
+ INSIST(HEAPCONDITION(i));
+ heap_check(heap);
+}
+
+static void
+sink_down(isc_heap_t *heap, unsigned int i, void *elt) {
+ unsigned int j, size, half_size;
+ size = heap->last;
+ half_size = size / 2;
+ while (i <= half_size) {
+ /* Find the smallest of the (at most) two children. */
+ j = heap_left(i);
+ if (j < size &&
+ heap->compare(heap->array[j + 1], heap->array[j]))
+ {
+ j++;
+ }
+ if (heap->compare(elt, heap->array[j])) {
+ break;
+ }
+ heap->array[i] = heap->array[j];
+ if (heap->index != NULL) {
+ (heap->index)(heap->array[i], i);
+ }
+ i = j;
+ }
+ heap->array[i] = elt;
+ if (heap->index != NULL) {
+ (heap->index)(heap->array[i], i);
+ }
+
+ INSIST(HEAPCONDITION(i));
+ heap_check(heap);
+}
+
+void
+isc_heap_insert(isc_heap_t *heap, void *elt) {
+ unsigned int new_last;
+
+ REQUIRE(VALID_HEAP(heap));
+
+ heap_check(heap);
+ new_last = heap->last + 1;
+ RUNTIME_CHECK(new_last > 0); /* overflow check */
+ if (new_last >= heap->size) {
+ resize(heap);
+ }
+ heap->last = new_last;
+
+ float_up(heap, new_last, elt);
+}
+
+void
+isc_heap_delete(isc_heap_t *heap, unsigned int idx) {
+ void *elt;
+ bool less;
+
+ REQUIRE(VALID_HEAP(heap));
+ REQUIRE(idx >= 1 && idx <= heap->last);
+
+ heap_check(heap);
+ if (heap->index != NULL) {
+ (heap->index)(heap->array[idx], 0);
+ }
+ if (idx == heap->last) {
+ heap->array[heap->last] = NULL;
+ heap->last--;
+ heap_check(heap);
+ } else {
+ elt = heap->array[heap->last];
+ heap->array[heap->last] = NULL;
+ heap->last--;
+
+ less = heap->compare(elt, heap->array[idx]);
+ heap->array[idx] = elt;
+ if (less) {
+ float_up(heap, idx, heap->array[idx]);
+ } else {
+ sink_down(heap, idx, heap->array[idx]);
+ }
+ }
+}
+
+void
+isc_heap_increased(isc_heap_t *heap, unsigned int idx) {
+ REQUIRE(VALID_HEAP(heap));
+ REQUIRE(idx >= 1 && idx <= heap->last);
+
+ float_up(heap, idx, heap->array[idx]);
+}
+
+void
+isc_heap_decreased(isc_heap_t *heap, unsigned int idx) {
+ REQUIRE(VALID_HEAP(heap));
+ REQUIRE(idx >= 1 && idx <= heap->last);
+
+ sink_down(heap, idx, heap->array[idx]);
+}
+
+void *
+isc_heap_element(isc_heap_t *heap, unsigned int idx) {
+ REQUIRE(VALID_HEAP(heap));
+ REQUIRE(idx >= 1);
+
+ heap_check(heap);
+ if (idx <= heap->last) {
+ return (heap->array[idx]);
+ }
+ return (NULL);
+}
+
+void
+isc_heap_foreach(isc_heap_t *heap, isc_heapaction_t action, void *uap) {
+ unsigned int i;
+
+ REQUIRE(VALID_HEAP(heap));
+ REQUIRE(action != NULL);
+
+ for (i = 1; i <= heap->last; i++) {
+ (action)(heap->array[i], uap);
+ }
+}
diff --git a/lib/isc/hex.c b/lib/isc/hex.c
new file mode 100644
index 0000000..be67f03
--- /dev/null
+++ b/lib/isc/hex.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.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/hex.h>
+#include <isc/lex.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#define RETERR(x) \
+ do { \
+ isc_result_t _r = (x); \
+ if (_r != ISC_R_SUCCESS) \
+ return ((_r)); \
+ } while (0)
+
+/*
+ * BEW: These static functions are copied from lib/dns/rdata.c.
+ */
+static isc_result_t
+str_totext(const char *source, isc_buffer_t *target);
+
+static isc_result_t
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length);
+
+static const char hex[] = "0123456789ABCDEF";
+
+isc_result_t
+isc_hex_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target) {
+ char buf[3];
+ unsigned int loops = 0;
+
+ if (wordlength < 2) {
+ wordlength = 2;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ while (source->length > 0) {
+ buf[0] = hex[(source->base[0] >> 4) & 0xf];
+ buf[1] = hex[(source->base[0]) & 0xf];
+ RETERR(str_totext(buf, target));
+ isc_region_consume(source, 1);
+
+ loops++;
+ if (source->length != 0 && (int)((loops + 1) * 2) >= wordlength)
+ {
+ loops = 0;
+ RETERR(str_totext(wordbreak, target));
+ }
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * State of a hex decoding process in progress.
+ */
+typedef struct {
+ int length; /*%< Desired length of binary data or -1 */
+ isc_buffer_t *target; /*%< Buffer for resulting binary data */
+ int digits; /*%< Number of buffered hex digits */
+ int val[2];
+} hex_decode_ctx_t;
+
+static void
+hex_decode_init(hex_decode_ctx_t *ctx, int length, isc_buffer_t *target) {
+ ctx->digits = 0;
+ ctx->length = length;
+ ctx->target = target;
+}
+
+static isc_result_t
+hex_decode_char(hex_decode_ctx_t *ctx, int c) {
+ const char *s;
+
+ if ((s = strchr(hex, toupper(c))) == NULL) {
+ return (ISC_R_BADHEX);
+ }
+ ctx->val[ctx->digits++] = (int)(s - hex);
+ if (ctx->digits == 2) {
+ unsigned char num;
+
+ num = (ctx->val[0] << 4) + (ctx->val[1]);
+ RETERR(mem_tobuffer(ctx->target, &num, 1));
+ if (ctx->length >= 0) {
+ if (ctx->length == 0) {
+ return (ISC_R_BADHEX);
+ } else {
+ ctx->length -= 1;
+ }
+ }
+ ctx->digits = 0;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+hex_decode_finish(hex_decode_ctx_t *ctx) {
+ if (ctx->length > 0) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (ctx->digits != 0) {
+ return (ISC_R_BADHEX);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length) {
+ unsigned int before, after;
+ hex_decode_ctx_t ctx;
+ isc_textregion_t *tr;
+ isc_token_t token;
+ bool eol;
+
+ REQUIRE(length >= -2);
+
+ hex_decode_init(&ctx, length, target);
+
+ before = isc_buffer_usedlength(target);
+ while (ctx.length != 0) {
+ unsigned int i;
+
+ if (length > 0) {
+ eol = false;
+ } else {
+ eol = true;
+ }
+ RETERR(isc_lex_getmastertoken(lexer, &token,
+ isc_tokentype_string, eol));
+ if (token.type != isc_tokentype_string) {
+ break;
+ }
+ tr = &token.value.as_textregion;
+ for (i = 0; i < tr->length; i++) {
+ RETERR(hex_decode_char(&ctx, tr->base[i]));
+ }
+ }
+ after = isc_buffer_usedlength(target);
+ if (ctx.length < 0) {
+ isc_lex_ungettoken(lexer, &token);
+ }
+ RETERR(hex_decode_finish(&ctx));
+ if (length == -2 && before == after) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hex_decodestring(const char *cstr, isc_buffer_t *target) {
+ hex_decode_ctx_t ctx;
+
+ hex_decode_init(&ctx, -1, target);
+ for (;;) {
+ int c = *cstr++;
+ if (c == '\0') {
+ break;
+ }
+ if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
+ continue;
+ }
+ RETERR(hex_decode_char(&ctx, c));
+ }
+ RETERR(hex_decode_finish(&ctx));
+ return (ISC_R_SUCCESS);
+}
+
+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
+mem_tobuffer(isc_buffer_t *target, void *base, unsigned int length) {
+ isc_region_t tr;
+
+ isc_buffer_availableregion(target, &tr);
+ if (length > tr.length) {
+ return (ISC_R_NOSPACE);
+ }
+ memmove(tr.base, base, length);
+ isc_buffer_add(target, length);
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/hmac.c b/lib/isc/hmac.c
new file mode 100644
index 0000000..bc35bef
--- /dev/null
+++ b/lib/isc/hmac.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.
+ */
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+
+#include <isc/assertions.h>
+#include <isc/hmac.h>
+#include <isc/md.h>
+#include <isc/safe.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include "openssl_shim.h"
+
+isc_hmac_t *
+isc_hmac_new(void) {
+ EVP_MD_CTX *hmac = EVP_MD_CTX_new();
+ RUNTIME_CHECK(hmac != NULL);
+ return ((isc_hmac_t *)hmac);
+}
+
+void
+isc_hmac_free(isc_hmac_t *hmac) {
+ if (hmac == NULL) {
+ return;
+ }
+
+ EVP_MD_CTX_free((EVP_MD_CTX *)hmac);
+}
+
+isc_result_t
+isc_hmac_init(isc_hmac_t *hmac, const void *key, const size_t keylen,
+ const isc_md_type_t *md_type) {
+ EVP_PKEY *pkey;
+
+ REQUIRE(hmac != NULL);
+ REQUIRE(key != NULL);
+ REQUIRE(keylen <= INT_MAX);
+
+ if (md_type == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_HMAC, NULL, key, keylen);
+ if (pkey == NULL) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ if (EVP_DigestSignInit(hmac, NULL, md_type, NULL, pkey) != 1) {
+ EVP_PKEY_free(pkey);
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ EVP_PKEY_free(pkey);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hmac_reset(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ if (EVP_MD_CTX_reset(hmac) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hmac_update(isc_hmac_t *hmac, const unsigned char *buf, const size_t len) {
+ REQUIRE(hmac != NULL);
+
+ if (buf == NULL || len == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (EVP_DigestSignUpdate(hmac, buf, len) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_hmac_final(isc_hmac_t *hmac, unsigned char *digest,
+ unsigned int *digestlen) {
+ REQUIRE(hmac != NULL);
+ REQUIRE(digest != NULL);
+ REQUIRE(digestlen != NULL);
+
+ size_t len = *digestlen;
+
+ if (EVP_DigestSignFinal(hmac, digest, &len) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ *digestlen = (unsigned int)len;
+
+ return (ISC_R_SUCCESS);
+}
+
+const isc_md_type_t *
+isc_hmac_get_md_type(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return (EVP_MD_CTX_get0_md(hmac));
+}
+
+size_t
+isc_hmac_get_size(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return ((size_t)EVP_MD_CTX_size(hmac));
+}
+
+int
+isc_hmac_get_block_size(isc_hmac_t *hmac) {
+ REQUIRE(hmac != NULL);
+
+ return (EVP_MD_CTX_block_size(hmac));
+}
+
+isc_result_t
+isc_hmac(const isc_md_type_t *type, const void *key, const size_t keylen,
+ const unsigned char *buf, const size_t len, unsigned char *digest,
+ unsigned int *digestlen) {
+ isc_result_t res;
+ isc_hmac_t *hmac = isc_hmac_new();
+
+ res = isc_hmac_init(hmac, key, keylen, type);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ res = isc_hmac_update(hmac, buf, len);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ res = isc_hmac_final(hmac, digest, digestlen);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+end:
+ isc_hmac_free(hmac);
+
+ return (res);
+}
diff --git a/lib/isc/ht.c b/lib/isc/ht.c
new file mode 100644
index 0000000..eaf2b3c
--- /dev/null
+++ b/lib/isc/ht.c
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/hash.h>
+#include <isc/ht.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+typedef struct isc_ht_node isc_ht_node_t;
+
+#define ISC_HT_MAGIC ISC_MAGIC('H', 'T', 'a', 'b')
+#define ISC_HT_VALID(ht) ISC_MAGIC_VALID(ht, ISC_HT_MAGIC)
+
+#define HT_NO_BITS 0
+#define HT_MIN_BITS 1
+#define HT_MAX_BITS 32
+#define HT_OVERCOMMIT 3
+
+#define HT_NEXTTABLE(idx) ((idx == 0) ? 1 : 0)
+#define TRY_NEXTTABLE(idx, ht) (idx == ht->hindex && rehashing_in_progress(ht))
+
+#define GOLDEN_RATIO_32 0x61C88647
+
+#define HASHSIZE(bits) (UINT64_C(1) << (bits))
+
+struct isc_ht_node {
+ void *value;
+ isc_ht_node_t *next;
+ uint32_t hashval;
+ size_t keysize;
+ unsigned char key[];
+};
+
+struct isc_ht {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ size_t count;
+ bool case_sensitive;
+ size_t size[2];
+ uint8_t hashbits[2];
+ isc_ht_node_t **table[2];
+ uint8_t hindex;
+ uint32_t hiter; /* rehashing iterator */
+};
+
+struct isc_ht_iter {
+ isc_ht_t *ht;
+ size_t i;
+ uint8_t hindex;
+ isc_ht_node_t *cur;
+};
+
+static isc_ht_node_t *
+isc__ht_find(const isc_ht_t *ht, const unsigned char *key,
+ const uint32_t keysize, const uint32_t hashval, const uint8_t idx);
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ const uint32_t hashval, const uint8_t idx, void *value);
+static isc_result_t
+isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ const uint32_t hashval, const uint8_t idx);
+
+static uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount);
+
+static void
+hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits);
+static void
+hashtable_free(isc_ht_t *ht, const uint8_t idx);
+static void
+hashtable_rehash(isc_ht_t *ht, uint32_t newbits);
+static void
+hashtable_rehash_one(isc_ht_t *ht);
+static void
+maybe_rehash(isc_ht_t *ht, size_t newcount);
+
+static isc_result_t
+isc__ht_iter_next(isc_ht_iter_t *it);
+
+static bool
+isc__ht_node_match(isc_ht_node_t *node, const uint32_t hashval,
+ const uint8_t *key, uint32_t keysize) {
+ return (node->hashval == hashval && node->keysize == keysize &&
+ memcmp(node->key, key, keysize) == 0);
+}
+
+static uint32_t
+hash_32(uint32_t val, unsigned int bits) {
+ REQUIRE(bits <= HT_MAX_BITS);
+ /* High bits are more random. */
+ return (val * GOLDEN_RATIO_32 >> (32 - bits));
+}
+
+static bool
+rehashing_in_progress(const isc_ht_t *ht) {
+ return (ht->table[HT_NEXTTABLE(ht->hindex)] != NULL);
+}
+
+static bool
+hashtable_is_overcommited(isc_ht_t *ht) {
+ return (ht->count >= (ht->size[ht->hindex] * HT_OVERCOMMIT));
+}
+
+static uint32_t
+rehash_bits(isc_ht_t *ht, size_t newcount) {
+ uint32_t newbits = ht->hashbits[ht->hindex];
+
+ while (newcount >= HASHSIZE(newbits) && newbits <= HT_MAX_BITS) {
+ newbits += 1;
+ }
+
+ return (newbits);
+}
+
+/*
+ * Rebuild the hashtable to reduce the load factor
+ */
+static void
+hashtable_rehash(isc_ht_t *ht, uint32_t newbits) {
+ uint8_t oldindex = ht->hindex;
+ uint32_t oldbits = ht->hashbits[oldindex];
+ uint8_t newindex = HT_NEXTTABLE(oldindex);
+
+ REQUIRE(ht->hashbits[oldindex] >= HT_MIN_BITS);
+ REQUIRE(ht->hashbits[oldindex] <= HT_MAX_BITS);
+ REQUIRE(ht->table[oldindex] != NULL);
+
+ REQUIRE(newbits <= HT_MAX_BITS);
+ REQUIRE(ht->hashbits[newindex] == HT_NO_BITS);
+ REQUIRE(ht->table[newindex] == NULL);
+
+ REQUIRE(newbits > oldbits);
+
+ hashtable_new(ht, newindex, newbits);
+
+ ht->hindex = newindex;
+
+ hashtable_rehash_one(ht);
+}
+
+static void
+hashtable_rehash_one(isc_ht_t *ht) {
+ isc_ht_node_t **newtable = ht->table[ht->hindex];
+ uint32_t oldsize = ht->size[HT_NEXTTABLE(ht->hindex)];
+ isc_ht_node_t **oldtable = ht->table[HT_NEXTTABLE(ht->hindex)];
+ isc_ht_node_t *node = NULL;
+ isc_ht_node_t *nextnode;
+
+ /* Find first non-empty node */
+ while (ht->hiter < oldsize && oldtable[ht->hiter] == NULL) {
+ ht->hiter++;
+ }
+
+ /* Rehashing complete */
+ if (ht->hiter == oldsize) {
+ hashtable_free(ht, HT_NEXTTABLE(ht->hindex));
+ ht->hiter = 0;
+ return;
+ }
+
+ /* Move the first non-empty node from old hashtable to new hashtable */
+ for (node = oldtable[ht->hiter]; node != NULL; node = nextnode) {
+ uint32_t hash = hash_32(node->hashval,
+ ht->hashbits[ht->hindex]);
+ nextnode = node->next;
+ node->next = newtable[hash];
+ newtable[hash] = node;
+ }
+
+ oldtable[ht->hiter] = NULL;
+
+ ht->hiter++;
+}
+
+static void
+maybe_rehash(isc_ht_t *ht, size_t newcount) {
+ uint32_t newbits = rehash_bits(ht, newcount);
+
+ if (ht->hashbits[ht->hindex] < newbits && newbits <= HT_MAX_BITS) {
+ hashtable_rehash(ht, newbits);
+ }
+}
+
+static void
+hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits) {
+ size_t size;
+ REQUIRE(ht->hashbits[idx] == HT_NO_BITS);
+ REQUIRE(ht->table[idx] == NULL);
+ REQUIRE(bits >= HT_MIN_BITS);
+ REQUIRE(bits <= HT_MAX_BITS);
+
+ ht->hashbits[idx] = bits;
+ ht->size[idx] = HASHSIZE(ht->hashbits[idx]);
+
+ size = ht->size[idx] * sizeof(isc_ht_node_t *);
+
+ ht->table[idx] = isc_mem_get(ht->mctx, size);
+ memset(ht->table[idx], 0, size);
+}
+
+static void
+hashtable_free(isc_ht_t *ht, const uint8_t idx) {
+ size_t size = ht->size[idx] * sizeof(isc_ht_node_t *);
+
+ for (size_t i = 0; i < ht->size[idx]; i++) {
+ isc_ht_node_t *node = ht->table[idx][i];
+ while (node != NULL) {
+ isc_ht_node_t *next = node->next;
+ ht->count--;
+ isc_mem_put(ht->mctx, node,
+ sizeof(*node) + node->keysize);
+ node = next;
+ }
+ }
+
+ isc_mem_put(ht->mctx, ht->table[idx], size);
+ ht->hashbits[idx] = HT_NO_BITS;
+ ht->table[idx] = NULL;
+}
+
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits,
+ unsigned int options) {
+ isc_ht_t *ht = NULL;
+ bool case_sensitive = ((options & ISC_HT_CASE_INSENSITIVE) == 0);
+
+ REQUIRE(htp != NULL && *htp == NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(bits >= 1 && bits <= HT_MAX_BITS);
+
+ ht = isc_mem_get(mctx, sizeof(*ht));
+ *ht = (isc_ht_t){
+ .case_sensitive = case_sensitive,
+ };
+
+ isc_mem_attach(mctx, &ht->mctx);
+
+ hashtable_new(ht, 0, bits);
+
+ ht->magic = ISC_HT_MAGIC;
+
+ *htp = ht;
+}
+
+void
+isc_ht_destroy(isc_ht_t **htp) {
+ isc_ht_t *ht;
+
+ REQUIRE(htp != NULL);
+ REQUIRE(ISC_HT_VALID(*htp));
+
+ ht = *htp;
+ *htp = NULL;
+ ht->magic = 0;
+
+ for (size_t i = 0; i <= 1; i++) {
+ if (ht->table[i] != NULL) {
+ hashtable_free(ht, i);
+ }
+ }
+
+ INSIST(ht->count == 0);
+
+ isc_mem_putanddetach(&ht->mctx, ht, sizeof(*ht));
+}
+
+static void
+isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ const uint32_t hashval, const uint8_t idx, void *value) {
+ isc_ht_node_t *node;
+ uint32_t hash;
+
+ hash = hash_32(hashval, ht->hashbits[idx]);
+
+ node = isc_mem_get(ht->mctx, sizeof(*node) + keysize);
+ *node = (isc_ht_node_t){
+ .keysize = keysize,
+ .hashval = hashval,
+ .next = ht->table[idx][hash],
+ .value = value,
+ };
+
+ memmove(node->key, key, keysize);
+
+ ht->count++;
+ ht->table[idx][hash] = node;
+}
+
+isc_result_t
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ void *value) {
+ uint32_t hashval;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+
+ if (rehashing_in_progress(ht)) {
+ /* Rehash in progress */
+ hashtable_rehash_one(ht);
+ } else if (hashtable_is_overcommited(ht)) {
+ /* Rehash requested */
+ maybe_rehash(ht, ht->count);
+ }
+
+ hashval = isc_hash32(key, keysize, ht->case_sensitive);
+
+ if (isc__ht_find(ht, key, keysize, hashval, ht->hindex) != NULL) {
+ return (ISC_R_EXISTS);
+ }
+
+ isc__ht_add(ht, key, keysize, hashval, ht->hindex, value);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_ht_node_t *
+isc__ht_find(const isc_ht_t *ht, const unsigned char *key,
+ const uint32_t keysize, const uint32_t hashval,
+ const uint8_t idx) {
+ uint32_t hash;
+ uint8_t findex = idx;
+
+nexttable:
+ hash = hash_32(hashval, ht->hashbits[findex]);
+ for (isc_ht_node_t *node = ht->table[findex][hash]; node != NULL;
+ node = node->next)
+ {
+ if (isc__ht_node_match(node, hashval, key, keysize)) {
+ return (node);
+ }
+ }
+ if (TRY_NEXTTABLE(findex, ht)) {
+ /*
+ * Rehashing in progress, check the other table
+ */
+ findex = HT_NEXTTABLE(findex);
+ goto nexttable;
+ }
+
+ return (NULL);
+}
+
+isc_result_t
+isc_ht_find(const isc_ht_t *ht, const unsigned char *key,
+ const uint32_t keysize, void **valuep) {
+ uint32_t hashval;
+ isc_ht_node_t *node;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+ REQUIRE(valuep == NULL || *valuep == NULL);
+
+ hashval = isc_hash32(key, keysize, ht->case_sensitive);
+
+ node = isc__ht_find(ht, key, keysize, hashval, ht->hindex);
+ if (node == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (valuep != NULL) {
+ *valuep = node->value;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ const uint32_t hashval, const uint8_t idx) {
+ isc_ht_node_t *prev = NULL;
+ uint32_t hash;
+
+ hash = hash_32(hashval, ht->hashbits[idx]);
+
+ for (isc_ht_node_t *node = ht->table[idx][hash]; node != NULL;
+ prev = node, node = node->next)
+ {
+ if (isc__ht_node_match(node, hashval, key, keysize)) {
+ if (prev == NULL) {
+ ht->table[idx][hash] = node->next;
+ } else {
+ prev->next = node->next;
+ }
+ isc_mem_put(ht->mctx, node,
+ sizeof(*node) + node->keysize);
+ ht->count--;
+
+ return (ISC_R_SUCCESS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize) {
+ uint32_t hashval;
+ uint8_t hindex;
+ isc_result_t result;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(key != NULL && keysize > 0);
+
+ if (rehashing_in_progress(ht)) {
+ /* Rehash in progress */
+ hashtable_rehash_one(ht);
+ }
+
+ hindex = ht->hindex;
+ hashval = isc_hash32(key, keysize, ht->case_sensitive);
+nexttable:
+ result = isc__ht_delete(ht, key, keysize, hashval, hindex);
+
+ if (result == ISC_R_NOTFOUND && TRY_NEXTTABLE(hindex, ht)) {
+ /*
+ * Rehashing in progress, check the other table
+ */
+ hindex = HT_NEXTTABLE(hindex);
+ goto nexttable;
+ }
+
+ return (result);
+}
+
+void
+isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) {
+ isc_ht_iter_t *it;
+
+ REQUIRE(ISC_HT_VALID(ht));
+ REQUIRE(itp != NULL && *itp == NULL);
+
+ it = isc_mem_get(ht->mctx, sizeof(isc_ht_iter_t));
+ *it = (isc_ht_iter_t){
+ .ht = ht,
+ .hindex = ht->hindex,
+ };
+
+ *itp = it;
+}
+
+void
+isc_ht_iter_destroy(isc_ht_iter_t **itp) {
+ isc_ht_iter_t *it;
+ isc_ht_t *ht;
+
+ REQUIRE(itp != NULL && *itp != NULL);
+
+ it = *itp;
+ *itp = NULL;
+ ht = it->ht;
+ isc_mem_put(ht->mctx, it, sizeof(*it));
+}
+
+isc_result_t
+isc_ht_iter_first(isc_ht_iter_t *it) {
+ isc_ht_t *ht;
+
+ REQUIRE(it != NULL);
+
+ ht = it->ht;
+
+ it->hindex = ht->hindex;
+ it->i = 0;
+
+ return (isc__ht_iter_next(it));
+}
+
+static isc_result_t
+isc__ht_iter_next(isc_ht_iter_t *it) {
+ isc_ht_t *ht = it->ht;
+
+ while (it->i < ht->size[it->hindex] &&
+ ht->table[it->hindex][it->i] == NULL)
+ {
+ it->i++;
+ }
+
+ if (it->i < ht->size[it->hindex]) {
+ it->cur = ht->table[it->hindex][it->i];
+
+ return (ISC_R_SUCCESS);
+ }
+
+ if (TRY_NEXTTABLE(it->hindex, ht)) {
+ it->hindex = HT_NEXTTABLE(it->hindex);
+ it->i = 0;
+ return (isc__ht_iter_next(it));
+ }
+
+ return (ISC_R_NOMORE);
+}
+
+isc_result_t
+isc_ht_iter_next(isc_ht_iter_t *it) {
+ REQUIRE(it != NULL);
+ REQUIRE(it->cur != NULL);
+
+ it->cur = it->cur->next;
+
+ if (it->cur != NULL) {
+ return (ISC_R_SUCCESS);
+ }
+
+ it->i++;
+
+ return (isc__ht_iter_next(it));
+}
+
+isc_result_t
+isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_ht_node_t *dnode = NULL;
+ uint8_t dindex;
+ isc_ht_t *ht;
+ isc_result_t dresult;
+
+ REQUIRE(it != NULL);
+ REQUIRE(it->cur != NULL);
+
+ ht = it->ht;
+ dnode = it->cur;
+ dindex = it->hindex;
+
+ result = isc_ht_iter_next(it);
+
+ dresult = isc__ht_delete(ht, dnode->key, dnode->keysize, dnode->hashval,
+ dindex);
+ INSIST(dresult == ISC_R_SUCCESS);
+
+ return (result);
+}
+
+void
+isc_ht_iter_current(isc_ht_iter_t *it, void **valuep) {
+ REQUIRE(it != NULL);
+ REQUIRE(it->cur != NULL);
+ REQUIRE(valuep != NULL && *valuep == NULL);
+
+ *valuep = it->cur->value;
+}
+
+void
+isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key,
+ size_t *keysize) {
+ REQUIRE(it != NULL);
+ REQUIRE(it->cur != NULL);
+ REQUIRE(key != NULL && *key == NULL);
+
+ *key = it->cur->key;
+ *keysize = it->cur->keysize;
+}
+
+size_t
+isc_ht_count(const isc_ht_t *ht) {
+ REQUIRE(ISC_HT_VALID(ht));
+
+ return (ht->count);
+}
diff --git a/lib/isc/httpd.c b/lib/isc/httpd.c
new file mode 100644
index 0000000..b15cc45
--- /dev/null
+++ b/lib/isc/httpd.c
@@ -0,0 +1,1147 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <string.h>
+
+#include <isc/buffer.h>
+#include <isc/httpd.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/url.h>
+#include <isc/util.h>
+
+#include "netmgr/netmgr-int.h"
+#include "picohttpparser.h"
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+#endif /* ifdef HAVE_ZLIB */
+
+#define CHECK(m) \
+ do { \
+ result = (m); \
+ if (result != ISC_R_SUCCESS) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
+/*
+ * Size the recv buffer to hold at maximum two full buffers from isc_nm_read(),
+ * so we don't have to handle the truncation.
+ */
+#define HTTP_RECVLEN ISC_NETMGR_TCP_RECVBUF_SIZE * 2
+#define HTTP_SENDLEN ISC_NETMGR_TCP_RECVBUF_SIZE
+#define HTTP_HEADERS_NUM 100
+#define HTTP_MAX_REQUEST_LEN 4096
+
+typedef enum httpd_flags {
+ CONNECTION_CLOSE = 1 << 0, /* connection must close */
+ CONNECTION_KEEP_ALIVE = 1 << 1, /* response needs a keep-alive header */
+ ACCEPT_DEFLATE = 1 << 2, /* response can be compressed */
+} httpd_flags_t;
+
+#define HTTPD_MAGIC ISC_MAGIC('H', 't', 'p', 'd')
+#define VALID_HTTPD(m) ISC_MAGIC_VALID(m, HTTPD_MAGIC)
+
+#define HTTPDMGR_MAGIC ISC_MAGIC('H', 'p', 'd', 'm')
+#define VALID_HTTPDMGR(m) ISC_MAGIC_VALID(m, HTTPDMGR_MAGIC)
+
+/*%
+ * HTTP methods.
+ */
+typedef enum { METHOD_UNKNOWN = 0, METHOD_GET = 1, METHOD_POST = 2 } method_t;
+
+/*%
+ * HTTP urls. These are the URLs we manage, and the function to call to
+ * provide the data for it. We pass in the base url (so the same function
+ * can handle multiple requests), and a structure to fill in to return a
+ * result to the client. We also pass in a pointer to be filled in for
+ * the data cleanup function.
+ */
+struct isc_httpdurl {
+ char *url;
+ isc_httpdaction_t *action;
+ void *action_arg;
+ bool isstatic;
+ isc_time_t loadtime;
+ ISC_LINK(isc_httpdurl_t) link;
+};
+
+/*% http client */
+struct isc_httpd {
+ unsigned int magic; /* HTTPD_MAGIC */
+
+ isc_httpdmgr_t *mgr; /*%< our parent */
+ ISC_LINK(isc_httpd_t) link;
+
+ isc_nmhandle_t *handle; /* Permanent pointer to handle */
+ isc_nmhandle_t *readhandle; /* Waiting for a read callback */
+
+ /*%
+ * Received data state.
+ */
+ char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
+ size_t recvlen; /*%< length recv'd */
+ size_t consume; /*%< length of last command */
+
+ method_t method;
+ int minor_version;
+ httpd_flags_t flags;
+ const char *path;
+ isc_url_parser_t up;
+ isc_time_t if_modified_since;
+};
+
+struct isc_httpdmgr {
+ unsigned int magic; /* HTTPDMGR_MAGIC */
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ isc_nmsocket_t *sock;
+
+ isc_httpdclientok_t *client_ok; /*%< client validator */
+ isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */
+ void *cb_arg; /*%< argument for the above */
+
+ unsigned int flags;
+ ISC_LIST(isc_httpd_t) running; /*%< running clients */
+
+ isc_mutex_t lock;
+
+ ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */
+ isc_httpdaction_t *render_404;
+ isc_httpdaction_t *render_500;
+};
+
+typedef struct isc_httpd_sendreq {
+ isc_mem_t *mctx;
+ isc_httpd_t *httpd;
+ isc_nmhandle_t *handle;
+
+ /*%
+ * Transmit data state.
+ *
+ * This is the data buffer we will transmit.
+ *
+ * This free function pointer is filled in by the rendering function
+ * we call. The free function is called after the data is transmitted
+ * to the client.
+ *
+ * We currently use three buffers total:
+ *
+ * sendbuffer - gets filled as we gather the data
+ *
+ * bodybuffer - for the client to fill in (which it manages, it provides
+ * the space for it, etc) -- we will pass that buffer structure back to
+ * the caller, who is responsible for managing the space it may have
+ * allocated as backing store for it. we only allocate the buffer
+ * itself, not the backing store.
+ *
+ * compbuffer - managed by us, that contains the compressed HTTP data,
+ * if compression is used.
+ */
+ isc_buffer_t *sendbuffer;
+ isc_buffer_t *compbuffer;
+
+ isc_buffer_t bodybuffer;
+
+ const char *mimetype;
+ unsigned int retcode;
+ const char *retmsg;
+ isc_httpdfree_t *freecb;
+ void *freecb_arg;
+
+} isc_httpd_sendreq_t;
+
+static isc_result_t
+httpd_newconn(isc_nmhandle_t *, isc_result_t, void *);
+static void
+httpd_request(isc_nmhandle_t *, isc_result_t, isc_region_t *, void *);
+static void
+httpd_senddone(isc_nmhandle_t *, isc_result_t, void *);
+static void
+httpd_reset(void *);
+static void
+httpd_put(void *);
+
+static void
+httpd_addheader(isc_httpd_sendreq_t *, const char *, const char *);
+static void
+httpd_addheaderuint(isc_httpd_sendreq_t *, const char *, int);
+static void
+httpd_endheaders(isc_httpd_sendreq_t *);
+static void
+httpd_response(isc_httpd_t *, isc_httpd_sendreq_t *);
+
+static isc_result_t
+process_request(isc_httpd_t *, size_t);
+
+static isc_httpdaction_t render_404;
+static isc_httpdaction_t render_500;
+
+#if ENABLE_AFL
+static void (*finishhook)(void) = NULL;
+#endif /* ENABLE_AFL */
+
+static void
+destroy_httpdmgr(isc_httpdmgr_t *);
+
+static void
+httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **);
+static void
+httpdmgr_detach(isc_httpdmgr_t **);
+
+isc_result_t
+isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr,
+ isc_httpdclientok_t *client_ok,
+ isc_httpdondestroy_t *ondestroy, void *cb_arg,
+ isc_httpdmgr_t **httpdmgrp) {
+ isc_result_t result;
+ isc_httpdmgr_t *httpdmgr = NULL;
+
+ REQUIRE(nm != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
+
+ httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
+ *httpdmgr = (isc_httpdmgr_t){ .client_ok = client_ok,
+ .ondestroy = ondestroy,
+ .cb_arg = cb_arg,
+ .render_404 = render_404,
+ .render_500 = render_500 };
+
+ isc_mutex_init(&httpdmgr->lock);
+ isc_mem_attach(mctx, &httpdmgr->mctx);
+
+ ISC_LIST_INIT(httpdmgr->running);
+ ISC_LIST_INIT(httpdmgr->urls);
+
+ isc_refcount_init(&httpdmgr->references, 1);
+
+ CHECK(isc_nm_listentcp(nm, addr, httpd_newconn, httpdmgr,
+ sizeof(isc_httpd_t), 5, NULL, &httpdmgr->sock));
+
+ httpdmgr->magic = HTTPDMGR_MAGIC;
+ *httpdmgrp = httpdmgr;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ httpdmgr->magic = 0;
+ isc_refcount_decrementz(&httpdmgr->references);
+ isc_refcount_destroy(&httpdmgr->references);
+ isc_mem_detach(&httpdmgr->mctx);
+ isc_mutex_destroy(&httpdmgr->lock);
+ isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
+
+ return (result);
+}
+
+static void
+httpdmgr_attach(isc_httpdmgr_t *source, isc_httpdmgr_t **targetp) {
+ REQUIRE(VALID_HTTPDMGR(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) {
+ isc_httpdmgr_t *httpdmgr = NULL;
+
+ REQUIRE(httpdmgrp != NULL);
+ REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
+
+ httpdmgr = *httpdmgrp;
+ *httpdmgrp = NULL;
+
+ if (isc_refcount_decrement(&httpdmgr->references) == 1) {
+ destroy_httpdmgr(httpdmgr);
+ }
+}
+
+static void
+destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) {
+ isc_httpdurl_t *url;
+
+ isc_refcount_destroy(&httpdmgr->references);
+
+ LOCK(&httpdmgr->lock);
+
+ REQUIRE((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0);
+ REQUIRE(ISC_LIST_EMPTY(httpdmgr->running));
+
+ httpdmgr->magic = 0;
+
+ if (httpdmgr->sock != NULL) {
+ isc_nmsocket_close(&httpdmgr->sock);
+ }
+
+ /*
+ * Clear out the list of all actions we know about. Just free the
+ * memory.
+ */
+ url = ISC_LIST_HEAD(httpdmgr->urls);
+ while (url != NULL) {
+ isc_mem_free(httpdmgr->mctx, url->url);
+ ISC_LIST_UNLINK(httpdmgr->urls, url, link);
+ isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t));
+ url = ISC_LIST_HEAD(httpdmgr->urls);
+ }
+
+ UNLOCK(&httpdmgr->lock);
+ isc_mutex_destroy(&httpdmgr->lock);
+
+ if (httpdmgr->ondestroy != NULL) {
+ (httpdmgr->ondestroy)(httpdmgr->cb_arg);
+ }
+ isc_mem_putanddetach(&httpdmgr->mctx, httpdmgr, sizeof(isc_httpdmgr_t));
+}
+
+static bool
+name_match(const struct phr_header *header, const char *match) {
+ size_t match_len = strlen(match);
+ if (match_len != header->name_len) {
+ return (false);
+ }
+ return (strncasecmp(header->name, match, match_len) == 0);
+}
+
+static bool
+value_match(const struct phr_header *header, const char *match) {
+ size_t match_len = strlen(match);
+ size_t limit;
+
+ if (match_len > header->value_len) {
+ return (false);
+ }
+
+ limit = header->value_len - match_len + 1;
+
+ for (size_t i = 0; i < limit; i++) {
+ if (isspace(header->value[i])) {
+ while (i < limit && isspace(header->value[i])) {
+ i++;
+ }
+ continue;
+ }
+
+ if (strncasecmp(&header->value[i], match, match_len) == 0) {
+ i += match_len;
+ /*
+ * Sanity check; f.e. for 'deflate' match only
+ * 'deflate[,;]', but not 'deflateyou'
+ */
+ if (i == header->value_len || header->value[i] == ',' ||
+ header->value[i] == ';')
+ {
+ return (true);
+ }
+ }
+
+ while (i < limit && header->value[i] != ',') {
+ i++;
+ }
+ }
+ return (false);
+}
+
+static isc_result_t
+process_request(isc_httpd_t *httpd, size_t last_len) {
+ int pret;
+ const char *method = NULL;
+ size_t method_len = 0;
+ const char *path;
+ size_t path_len = 0;
+ struct phr_header headers[HTTP_HEADERS_NUM];
+ size_t num_headers;
+ isc_result_t result;
+
+ num_headers = ARRAY_SIZE(headers);
+
+ pret = phr_parse_request(httpd->recvbuf, httpd->recvlen, &method,
+ &method_len, &path, &path_len,
+ &httpd->minor_version, headers, &num_headers,
+ last_len);
+
+ if (pret == -1) {
+ /* Parse Error */
+ return (ISC_R_UNEXPECTED);
+ } else if (pret == -2) {
+ /* Need more data */
+ return (ISC_R_NOMORE);
+ }
+
+ INSIST(pret > 0);
+
+ if (pret > HTTP_MAX_REQUEST_LEN) {
+ return (ISC_R_RANGE);
+ }
+
+ httpd->consume = pret;
+
+ /*
+ * Determine if this is a POST or GET method. Any other values will
+ * cause an error to be returned.
+ */
+ if (strncmp(method, "GET ", method_len) == 0) {
+ httpd->method = METHOD_GET;
+ } else if (strncmp(method, "POST ", method_len) == 0) {
+ httpd->method = METHOD_POST;
+ } else {
+ return (ISC_R_RANGE);
+ }
+
+ /*
+ * Parse the URL
+ */
+ result = isc_url_parse(path, path_len, 0, &httpd->up);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ httpd->path = path;
+
+ /*
+ * Examine headers that can affect this request's response
+ */
+ httpd->flags = 0;
+
+ size_t content_len = 0;
+ bool keep_alive = false;
+ bool host_header = false;
+
+ isc_time_set(&httpd->if_modified_since, 0, 0);
+
+ for (size_t i = 0; i < num_headers; i++) {
+ struct phr_header *header = &headers[i];
+
+ if (name_match(header, "Content-Length")) {
+ char *endptr;
+ long val = strtol(header->value, &endptr, 10);
+
+ errno = 0;
+
+ /* ensure we consumed all digits */
+ if ((header->value + header->value_len) != endptr) {
+ return (ISC_R_BADNUMBER);
+ }
+ /* ensure there was no minus sign */
+ if (val < 0) {
+ return (ISC_R_BADNUMBER);
+ }
+ /* ensure it did not overflow */
+ if (errno != 0) {
+ return (ISC_R_RANGE);
+ }
+ content_len = val;
+ } else if (name_match(header, "Connection")) {
+ /* multiple fields in a connection header are allowed */
+ if (value_match(header, "close")) {
+ httpd->flags |= CONNECTION_CLOSE;
+ }
+ if (value_match(header, "keep-alive")) {
+ keep_alive = true;
+ }
+ } else if (name_match(header, "Host")) {
+ host_header = true;
+ } else if (name_match(header, "Accept-Encoding")) {
+ if (value_match(header, "deflate")) {
+ httpd->flags |= ACCEPT_DEFLATE;
+ }
+ } else if (name_match(header, "If-Modified-Since") &&
+ header->value_len < ISC_FORMATHTTPTIMESTAMP_SIZE)
+ {
+ char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1];
+ memmove(timestamp, header->value, header->value_len);
+ timestamp[header->value_len] = 0;
+
+ /* Ignore the value if it can't be parsed */
+ (void)isc_time_parsehttptimestamp(
+ timestamp, &httpd->if_modified_since);
+ }
+ }
+
+ /*
+ * The Content-Length is optional in an HTTP request.
+ * For a GET the length must be zero.
+ */
+ if (httpd->method == METHOD_GET && content_len != 0) {
+ return (ISC_R_BADNUMBER);
+ }
+
+ if (content_len >= HTTP_MAX_REQUEST_LEN) {
+ return (ISC_R_RANGE);
+ }
+
+ size_t consume = httpd->consume + content_len;
+ if (consume > httpd->recvlen) {
+ /* The request data isn't complete yet. */
+ return (ISC_R_NOMORE);
+ }
+
+ /* Consume the request's data, which we do not use. */
+ httpd->consume = consume;
+
+ switch (httpd->minor_version) {
+ case 0:
+ /*
+ * RFC 9112 section 9.3 says close takes priority if
+ * keep-alive is also present
+ */
+ if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) {
+ httpd->flags |= CONNECTION_KEEP_ALIVE;
+ } else {
+ httpd->flags |= CONNECTION_CLOSE;
+ }
+ break;
+ case 1:
+ if (!host_header) {
+ return (ISC_R_RANGE);
+ }
+ break;
+ default:
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Looks like a a valid request, so now we know we won't have
+ * to process this buffer again. We can NULL-terminate the
+ * URL for the caller's benefit, and set recvlen to 0 so
+ * the next read will overwrite this one instead of appending
+ * to the buffer.
+ */
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+httpd_reset(void *arg) {
+ isc_httpd_t *httpd = (isc_httpd_t *)arg;
+ isc_httpdmgr_t *httpdmgr = NULL;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ httpdmgr = httpd->mgr;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ LOCK(&httpdmgr->lock);
+ ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
+ UNLOCK(&httpdmgr->lock);
+
+ httpd->recvbuf[0] = 0;
+ httpd->recvlen = 0;
+ httpd->consume = 0;
+ httpd->method = METHOD_UNKNOWN;
+ httpd->flags = 0;
+
+ httpd->minor_version = -1;
+ httpd->path = NULL;
+ httpd->up = (isc_url_parser_t){ 0 };
+ isc_time_set(&httpd->if_modified_since, 0, 0);
+}
+
+static void
+isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) {
+ /* Clean up buffers */
+
+ isc_buffer_free(&req->sendbuffer);
+
+ isc_mem_putanddetach(&req->mctx, req, sizeof(*req));
+}
+
+static isc_httpd_sendreq_t *
+isc__httpd_sendreq_new(isc_httpd_t *httpd) {
+ isc_httpdmgr_t *httpdmgr = httpd->mgr;
+ isc_httpd_sendreq_t *req;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ req = isc_mem_get(httpdmgr->mctx, sizeof(*req));
+ *req = (isc_httpd_sendreq_t){ 0 };
+
+ isc_mem_attach(httpdmgr->mctx, &req->mctx);
+
+ /*
+ * Initialize the buffer for our headers.
+ */
+ isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN);
+ isc_buffer_clear(req->sendbuffer);
+ isc_buffer_setautorealloc(req->sendbuffer, true);
+
+ isc_buffer_initnull(&req->bodybuffer);
+
+ return (req);
+}
+
+static void
+httpd_put(void *arg) {
+ isc_httpd_t *httpd = (isc_httpd_t *)arg;
+ isc_httpdmgr_t *mgr = NULL;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ mgr = httpd->mgr;
+ REQUIRE(VALID_HTTPDMGR(mgr));
+
+ httpd->magic = 0;
+ httpd->mgr = NULL;
+
+ isc_mem_put(mgr->mctx, httpd, sizeof(*httpd));
+
+ httpdmgr_detach(&mgr);
+
+#if ENABLE_AFL
+ if (finishhook != NULL) {
+ finishhook();
+ }
+#endif /* ENABLE_AFL */
+}
+
+static void
+new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) {
+ isc_httpd_t *httpd = NULL;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ httpd = isc_nmhandle_getdata(handle);
+ if (httpd == NULL) {
+ httpd = isc_mem_get(httpdmgr->mctx, sizeof(*httpd));
+ *httpd = (isc_httpd_t){ .handle = NULL };
+ httpdmgr_attach(httpdmgr, &httpd->mgr);
+ }
+
+ if (httpd->handle == NULL) {
+ isc_nmhandle_setdata(handle, httpd, httpd_reset, httpd_put);
+ httpd->handle = handle;
+ } else {
+ INSIST(httpd->handle == handle);
+ }
+
+ ISC_LINK_INIT(httpd, link);
+
+ httpd->magic = HTTPD_MAGIC;
+
+ LOCK(&httpdmgr->lock);
+ ISC_LIST_APPEND(httpdmgr->running, httpd, link);
+ UNLOCK(&httpdmgr->lock);
+
+ isc_nmhandle_attach(httpd->handle, &httpd->readhandle);
+ isc_nm_read(handle, httpd_request, httpdmgr);
+}
+
+static isc_result_t
+httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+ isc_httpdmgr_t *httpdmgr = (isc_httpdmgr_t *)arg;
+ isc_sockaddr_t peeraddr;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ if ((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) {
+ return (ISC_R_CANCELED);
+ } else if (result == ISC_R_CANCELED) {
+ isc_httpdmgr_shutdown(&httpdmgr);
+ return (result);
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ peeraddr = isc_nmhandle_peeraddr(handle);
+ if (httpdmgr->client_ok != NULL &&
+ !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg))
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ new_httpd(httpdmgr, handle);
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
+ unsigned int *retcode, const char **retmsg, const char **mimetype,
+ isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
+ static char msg[] = "No such URL.\r\n";
+
+ UNUSED(httpd);
+ UNUSED(urlinfo);
+ UNUSED(arg);
+
+ *retcode = 404;
+ *retmsg = "No such URL";
+ *mimetype = "text/plain";
+ isc_buffer_reinit(b, msg, strlen(msg));
+ isc_buffer_add(b, strlen(msg));
+ *freecb = NULL;
+ *freecb_args = NULL;
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+render_500(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
+ unsigned int *retcode, const char **retmsg, const char **mimetype,
+ isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
+ static char msg[] = "Internal server failure.\r\n";
+
+ UNUSED(httpd);
+ UNUSED(urlinfo);
+ UNUSED(arg);
+
+ *retcode = 500;
+ *retmsg = "Internal server failure";
+ *mimetype = "text/plain";
+ isc_buffer_reinit(b, msg, strlen(msg));
+ isc_buffer_add(b, strlen(msg));
+ *freecb = NULL;
+ *freecb_args = NULL;
+
+ return (ISC_R_SUCCESS);
+}
+
+#ifdef HAVE_ZLIB
+/*%<
+ * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
+ * if necessary.
+ *
+ * Requires:
+ *\li httpd a valid isc_httpd_t object
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- all is well.
+ *\li #ISC_R_NOMEMORY -- not enough memory to compress data
+ *\li #ISC_R_FAILURE -- error during compression or compressed
+ * data would be larger than input data
+ */
+static isc_result_t
+httpd_compress(isc_httpd_sendreq_t *req) {
+ z_stream zstr;
+ int ret, inputlen;
+
+ /*
+ * We're setting output buffer size to input size so it fails if the
+ * compressed data size would be bigger than the input size.
+ */
+ inputlen = isc_buffer_usedlength(&req->bodybuffer);
+ if (inputlen == 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen);
+ isc_buffer_clear(req->compbuffer);
+
+ zstr = (z_stream){
+ .total_in = inputlen,
+ .avail_out = inputlen,
+ .avail_in = inputlen,
+ .next_in = isc_buffer_base(&req->bodybuffer),
+ .next_out = isc_buffer_base(req->compbuffer),
+ };
+
+ ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
+ if (ret == Z_OK) {
+ ret = deflate(&zstr, Z_FINISH);
+ }
+ deflateEnd(&zstr);
+ if (ret == Z_STREAM_END) {
+ isc_buffer_add(req->compbuffer, zstr.total_out);
+ return (ISC_R_SUCCESS);
+ } else {
+ isc_buffer_free(&req->compbuffer);
+ return (ISC_R_FAILURE);
+ }
+}
+#endif /* ifdef HAVE_ZLIB */
+
+static void
+prepare_response(isc_httpdmgr_t *mgr, isc_httpd_t *httpd,
+ isc_httpd_sendreq_t **reqp) {
+ isc_httpd_sendreq_t *req = NULL;
+ isc_time_t now;
+ char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ const char *path = "/";
+ size_t path_len = 1;
+ bool is_compressed = false;
+ isc_httpdurl_t *url = NULL;
+ isc_result_t result;
+
+ REQUIRE(VALID_HTTPD(httpd));
+ REQUIRE(reqp != NULL && *reqp == NULL);
+
+ isc_time_now(&now);
+ isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
+
+ if (httpd->up.field_set & (1 << ISC_UF_PATH)) {
+ path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off];
+ path_len = httpd->up.field_data[ISC_UF_PATH].len;
+ }
+
+ LOCK(&mgr->lock);
+ url = ISC_LIST_HEAD(mgr->urls);
+ while (url != NULL) {
+ if (strncmp(path, url->url, path_len) == 0) {
+ break;
+ }
+ url = ISC_LIST_NEXT(url, link);
+ }
+ UNLOCK(&mgr->lock);
+
+ req = isc__httpd_sendreq_new(httpd);
+
+ if (url == NULL) {
+ result = mgr->render_404(httpd, NULL, NULL, &req->retcode,
+ &req->retmsg, &req->mimetype,
+ &req->bodybuffer, &req->freecb,
+ &req->freecb_arg);
+ } else {
+ result = url->action(httpd, url, url->action_arg, &req->retcode,
+ &req->retmsg, &req->mimetype,
+ &req->bodybuffer, &req->freecb,
+ &req->freecb_arg);
+ }
+ if (result != ISC_R_SUCCESS) {
+ result = mgr->render_500(httpd, url, NULL, &req->retcode,
+ &req->retmsg, &req->mimetype,
+ &req->bodybuffer, &req->freecb,
+ &req->freecb_arg);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+#ifdef HAVE_ZLIB
+ if ((httpd->flags & ACCEPT_DEFLATE) != 0) {
+ result = httpd_compress(req);
+ if (result == ISC_R_SUCCESS) {
+ is_compressed = true;
+ }
+ }
+#endif /* ifdef HAVE_ZLIB */
+
+ httpd_response(httpd, req);
+ /* RFC 9112 § 9.6: SHOULD send Connection: close in last response */
+ if ((httpd->flags & CONNECTION_CLOSE) != 0) {
+ httpd_addheader(req, "Connection", "close");
+ } else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) {
+ httpd_addheader(req, "Connection", "Keep-Alive");
+ }
+ httpd_addheader(req, "Content-Type", req->mimetype);
+ httpd_addheader(req, "Date", datebuf);
+ httpd_addheader(req, "Expires", datebuf);
+
+ if (url != NULL && url->isstatic) {
+ char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
+ isc_time_formathttptimestamp(&url->loadtime, loadbuf,
+ sizeof(loadbuf));
+ httpd_addheader(req, "Last-Modified", loadbuf);
+ httpd_addheader(req, "Cache-Control: public", NULL);
+ } else {
+ httpd_addheader(req, "Last-Modified", datebuf);
+ httpd_addheader(req, "Pragma: no-cache", NULL);
+ httpd_addheader(req, "Cache-Control: no-cache", NULL);
+ }
+
+ httpd_addheader(req, "Server: libisc", NULL);
+
+ if (is_compressed) {
+ httpd_addheader(req, "Content-Encoding", "deflate");
+ httpd_addheaderuint(req, "Content-Length",
+ isc_buffer_usedlength(req->compbuffer));
+ } else {
+ httpd_addheaderuint(req, "Content-Length",
+ isc_buffer_usedlength(&req->bodybuffer));
+ }
+
+ httpd_endheaders(req); /* done */
+
+ /*
+ * Append either the compressed or the non-compressed response body to
+ * the response headers and store the result in httpd->sendbuffer.
+ */
+ if (is_compressed) {
+ isc_buffer_putmem(req->sendbuffer,
+ isc_buffer_base(req->compbuffer),
+ isc_buffer_usedlength(req->compbuffer));
+ isc_buffer_free(&req->compbuffer);
+ } else {
+ isc_buffer_putmem(req->sendbuffer,
+ isc_buffer_base(&req->bodybuffer),
+ isc_buffer_usedlength(&req->bodybuffer));
+ }
+
+ /* Free the bodybuffer */
+ if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) {
+ req->freecb(&req->bodybuffer, req->freecb_arg);
+ }
+
+ /* Consume the request from the recv buffer. */
+ INSIST(httpd->consume != 0);
+ INSIST(httpd->consume <= httpd->recvlen);
+ if (httpd->consume < httpd->recvlen) {
+ memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume,
+ httpd->recvlen - httpd->consume);
+ }
+ httpd->recvlen -= httpd->consume;
+ httpd->consume = 0;
+
+ /*
+ * We don't need to attach to httpd here because it gets only cleaned
+ * when the last handle has been detached
+ */
+ req->httpd = httpd;
+
+ *reqp = req;
+}
+
+static void
+httpd_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *arg) {
+ isc_result_t result;
+ isc_httpdmgr_t *mgr = arg;
+ isc_httpd_t *httpd = NULL;
+ isc_httpd_sendreq_t *req = NULL;
+ isc_region_t r;
+ size_t last_len = 0;
+
+ httpd = isc_nmhandle_getdata(handle);
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ REQUIRE(httpd->handle == handle);
+
+ if (httpd->readhandle == NULL) {
+ /* The channel has been already closed, just bail out */
+ return;
+ }
+
+ if (eresult != ISC_R_SUCCESS) {
+ goto close_readhandle;
+ }
+
+ REQUIRE(httpd->readhandle == handle);
+
+ isc_nm_pauseread(httpd->readhandle);
+
+ /*
+ * If we are being called from httpd_senddone(), the last HTTP request
+ * was processed successfully, reset the last_len to 0, even if there's
+ * data in the httpd->recvbuf.
+ */
+ last_len = (region == NULL) ? 0 : httpd->recvlen;
+
+ /* Store the received data into the recvbuf */
+ if (region != NULL) {
+ if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) {
+ goto close_readhandle;
+ }
+
+ memmove(httpd->recvbuf + httpd->recvlen, region->base,
+ region->length);
+ httpd->recvlen += region->length;
+ }
+
+ result = process_request(httpd, last_len);
+
+ if (result == ISC_R_NOMORE) {
+ if (httpd->recvlen > HTTP_MAX_REQUEST_LEN) {
+ goto close_readhandle;
+ }
+
+ /* Wait for more data, the readhandle is still attached */
+ isc_nm_resumeread(httpd->readhandle);
+ return;
+ }
+
+ /* XXXFANF it would be more polite to reply 400 bad request */
+ if (result != ISC_R_SUCCESS) {
+ goto close_readhandle;
+ }
+
+ prepare_response(mgr, httpd, &req);
+
+ /*
+ * Determine total response size.
+ */
+ isc_buffer_usedregion(req->sendbuffer, &r);
+
+ isc_nmhandle_attach(httpd->handle, &req->handle);
+ isc_nm_send(httpd->handle, &r, httpd_senddone, req);
+ return;
+
+close_readhandle:
+ isc_nm_pauseread(httpd->readhandle);
+ isc_nmhandle_detach(&httpd->readhandle);
+}
+
+void
+isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
+ isc_httpdmgr_t *httpdmgr;
+ isc_httpd_t *httpd;
+
+ REQUIRE(httpdmgrp != NULL);
+ REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
+
+ httpdmgr = *httpdmgrp;
+ *httpdmgrp = NULL;
+
+ isc_nm_stoplistening(httpdmgr->sock);
+
+ LOCK(&httpdmgr->lock);
+ httpdmgr->flags |= ISC_HTTPDMGR_SHUTTINGDOWN;
+
+ httpd = ISC_LIST_HEAD(httpdmgr->running);
+ while (httpd != NULL) {
+ isc_nm_cancelread(httpd->readhandle);
+ httpd = ISC_LIST_NEXT(httpd, link);
+ }
+ UNLOCK(&httpdmgr->lock);
+
+ isc_nmsocket_close(&httpdmgr->sock);
+
+ httpdmgr_detach(&httpdmgr);
+}
+
+static void
+httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) {
+ isc_result_t result;
+
+ result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n",
+ httpd->minor_version, req->retcode,
+ req->retmsg);
+
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) {
+ isc_result_t result;
+
+ if (val != NULL) {
+ result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name,
+ val);
+ } else {
+ result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name);
+ }
+
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+httpd_endheaders(isc_httpd_sendreq_t *req) {
+ isc_result_t result;
+
+ result = isc_buffer_printf(req->sendbuffer, "\r\n");
+
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) {
+ isc_result_t result;
+
+ result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val);
+
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+}
+
+static void
+httpd_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+ isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg;
+ isc_httpd_t *httpd = req->httpd;
+
+ REQUIRE(VALID_HTTPD(httpd));
+
+ if (httpd->readhandle == NULL) {
+ goto detach;
+ }
+
+ if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) == 0)
+ {
+ /*
+ * Calling httpd_request() with region NULL restarts
+ * reading.
+ */
+ httpd_request(handle, ISC_R_SUCCESS, NULL, httpd->mgr);
+ } else {
+ isc_nm_cancelread(httpd->readhandle);
+ }
+
+detach:
+ isc_nmhandle_detach(&handle);
+
+ isc__httpd_sendreq_free(req);
+}
+
+isc_result_t
+isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
+ isc_httpdaction_t *func, void *arg) {
+ isc_httpdurl_t *item;
+
+ REQUIRE(VALID_HTTPDMGR(httpdmgr));
+
+ if (url == NULL) {
+ httpdmgr->render_404 = func;
+ return (ISC_R_SUCCESS);
+ }
+
+ item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
+
+ item->url = isc_mem_strdup(httpdmgr->mctx, url);
+
+ item->action = func;
+ item->action_arg = arg;
+ item->isstatic = isstatic;
+ isc_time_now(&item->loadtime);
+
+ ISC_LINK_INIT(item, link);
+
+ LOCK(&httpdmgr->lock);
+ ISC_LIST_APPEND(httpdmgr->urls, item, link);
+ UNLOCK(&httpdmgr->lock);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_httpd_setfinishhook(void (*fn)(void)) {
+#if ENABLE_AFL
+ finishhook = fn;
+#else /* ENABLE_AFL */
+ UNUSED(fn);
+#endif /* ENABLE_AFL */
+}
+
+bool
+isc_httpdurl_isstatic(const isc_httpdurl_t *url) {
+ return (url->isstatic);
+}
+
+const isc_time_t *
+isc_httpdurl_loadtime(const isc_httpdurl_t *url) {
+ return (&url->loadtime);
+}
+
+const isc_time_t *
+isc_httpd_if_modified_since(const isc_httpd_t *httpd) {
+ return ((const isc_time_t *)&httpd->if_modified_since);
+}
diff --git a/lib/isc/include/isc/aes.h b/lib/isc/include/isc/aes.h
new file mode 100644
index 0000000..9657494
--- /dev/null
+++ b/lib/isc/include/isc/aes.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.
+ */
+
+/*! \file isc/aes.h */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#define ISC_AES128_KEYLENGTH 16U
+#define ISC_AES192_KEYLENGTH 24U
+#define ISC_AES256_KEYLENGTH 32U
+#define ISC_AES_BLOCK_LENGTH 16U
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_aes128_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out);
+
+void
+isc_aes192_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out);
+
+void
+isc_aes256_crypt(const unsigned char *key, const unsigned char *in,
+ unsigned char *out);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/align.h b/lib/isc/include/isc/align.h
new file mode 100644
index 0000000..7b72e9d
--- /dev/null
+++ b/lib/isc/include/isc/align.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
+
+#ifdef HAVE_STDALIGN_H
+#include <stdalign.h>
+#else /* ifdef HAVE_STDALIGN_H */
+#define alignas(x) __attribute__((__aligned__(x)))
+#endif /* ifdef HAVE_STDALIGN_H */
diff --git a/lib/isc/include/isc/app.h b/lib/isc/include/isc/app.h
new file mode 100644
index 0000000..1a42bd0
--- /dev/null
+++ b/lib/isc/include/isc/app.h
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/app.h
+ * \brief ISC Application Support
+ *
+ * Dealing with program termination can be difficult, especially in a
+ * multithreaded program. The routines in this module help coordinate
+ * the shutdown process. They are used as follows by the initial (main)
+ * thread of the application:
+ *
+ *\li isc_app_start(); Call very early in main(), before
+ * any other threads have been created.
+ *
+ *\li isc_app_run(); This will post any on-run events,
+ * and then block until application
+ * shutdown is requested. A shutdown
+ * request is made by calling
+ * isc_app_shutdown(), or by sending
+ * SIGINT or SIGTERM to the process.
+ * After isc_app_run() returns, the
+ * application should shutdown itself.
+ *
+ *\li isc_app_finish(); Call very late in main().
+ *
+ * Applications that want to use SIGHUP/isc_app_reload() to trigger reloading
+ * should check the result of isc_app_run() and call the reload routine if
+ * the result is ISC_R_RELOAD. They should then call isc_app_run() again
+ * to resume waiting for reload or termination.
+ *
+ * Use of this module is not required. In particular, isc_app_start() is
+ * NOT an ISC library initialization routine.
+ *
+ * This module also supports per-thread 'application contexts'. With this
+ * mode, a thread-based application will have a separate context, in which
+ * it uses other ISC library services such as tasks or timers. Signals are
+ * not caught in this mode, so that the application can handle the signals
+ * in its preferred way.
+ *
+ * \li MP:
+ * Clients must ensure that isc_app_start(), isc_app_run(), and
+ * isc_app_finish() are called at most once. isc_app_shutdown()
+ * is safe to use by any thread (provided isc_app_start() has been
+ * called previously).
+ *
+ * The same note applies to isc_app_ctxXXX() functions, but in this case
+ * it's a per-thread restriction. For example, a thread with an
+ * application context must ensure that isc_app_ctxstart() with the
+ * context is called at most once.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * None.
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+#include <stdbool.h>
+
+#include <isc/eventclass.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+/***
+ *** Types
+ ***/
+
+typedef isc_event_t isc_appevent_t;
+
+#define ISC_APPEVENT_FIRSTEVENT (ISC_EVENTCLASS_APP + 0)
+#define ISC_APPEVENT_SHUTDOWN (ISC_EVENTCLASS_APP + 1)
+#define ISC_APPEVENT_LASTEVENT (ISC_EVENTCLASS_APP + 65535)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_app_ctxstart(isc_appctx_t *ctx);
+
+isc_result_t
+isc_app_start(void);
+/*!<
+ * \brief Start an ISC library application.
+ *
+ * Notes:
+ * This call should be made before any other ISC library call, and as
+ * close to the beginning of the application as possible.
+ *
+ * Requires:
+ *\li 'ctx' is a valid application context (for app_ctxstart()).
+ */
+
+isc_result_t
+isc_app_ctxonrun(isc_appctx_t *ctx, isc_mem_t *mctx, isc_task_t *task,
+ isc_taskaction_t action, void *arg);
+isc_result_t
+isc_app_onrun(isc_mem_t *mctx, isc_task_t *task, isc_taskaction_t action,
+ void *arg);
+/*!<
+ * \brief Request delivery of an event when the application is run.
+ *
+ * Requires:
+ *\li isc_app_start() has been called.
+ *\li 'ctx' is a valid application context (for app_ctxonrun()).
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOMEMORY
+ */
+
+isc_result_t
+isc_app_ctxrun(isc_appctx_t *ctx);
+
+isc_result_t
+isc_app_run(void);
+/*!<
+ * \brief Run an ISC library application.
+ *
+ * Notes:
+ *\li The caller (typically the initial thread of an application) will
+ * block until shutdown is requested. When the call returns, the
+ * caller should start shutting down the application.
+ *
+ * Requires:
+ *\li isc_app_[ctx]start() has been called.
+ *
+ * Ensures:
+ *\li Any events requested via isc_app_onrun() will have been posted (in
+ * FIFO order) before isc_app_run() blocks.
+ *\li 'ctx' is a valid application context (for app_ctxrun()).
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS Shutdown has been requested.
+ *\li ISC_R_RELOAD Reload has been requested.
+ */
+
+bool
+isc_app_isrunning(void);
+/*!<
+ * \brief Return if the ISC library application is running.
+ *
+ * Returns:
+ *\li true App is running.
+ *\li false App is not running.
+ */
+
+void
+isc_app_ctxshutdown(isc_appctx_t *ctx);
+
+void
+isc_app_shutdown(void);
+/*!<
+ * \brief Request application shutdown.
+ *
+ * Notes:
+ *\li It is safe to call isc_app_shutdown() multiple times. Shutdown will
+ * only be triggered once.
+ *
+ * Requires:
+ *\li isc_app_[ctx]run() has been called.
+ *\li 'ctx' is a valid application context (for app_ctxshutdown()).
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_UNEXPECTED
+ */
+
+void
+isc_app_ctxsuspend(isc_appctx_t *ctx);
+/*!<
+ * \brief This has the same behavior as isc_app_ctxsuspend().
+ */
+
+void
+isc_app_reload(void);
+/*!<
+ * \brief Request application reload.
+ *
+ * Requires:
+ *\li isc_app_run() has been called.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_UNEXPECTED
+ */
+
+void
+isc_app_ctxfinish(isc_appctx_t *ctx);
+
+void
+isc_app_finish(void);
+/*!<
+ * \brief Finish an ISC library application.
+ *
+ * Notes:
+ *\li This call should be made at or near the end of main().
+ *
+ * Requires:
+ *\li isc_app_start() has been called.
+ *\li 'ctx' is a valid application context (for app_ctxfinish()).
+ *
+ * Ensures:
+ *\li Any resources allocated by isc_app_start() have been released.
+ */
+
+void
+isc_app_block(void);
+/*!<
+ * \brief Indicate that a blocking operation will be performed.
+ *
+ * Notes:
+ *\li If a blocking operation is in process, a call to isc_app_shutdown()
+ * or an external signal will abort the program, rather than allowing
+ * clean shutdown. This is primarily useful for reading user input.
+ *
+ * Requires:
+ * \li isc_app_start() has been called.
+ * \li No other blocking operations are in progress.
+ */
+
+void
+isc_app_unblock(void);
+/*!<
+ * \brief Indicate that a blocking operation is complete.
+ *
+ * Notes:
+ * \li When a blocking operation has completed, return the program to a
+ * state where a call to isc_app_shutdown() or an external signal will
+ * shutdown normally.
+ *
+ * Requires:
+ * \li isc_app_start() has been called.
+ * \li isc_app_block() has been called by the same thread.
+ */
+
+isc_result_t
+isc_appctx_create(isc_mem_t *mctx, isc_appctx_t **ctxp);
+/*!<
+ * \brief Create an application context.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context.
+ *\li 'ctxp' != NULL && *ctxp == NULL.
+ */
+
+void
+isc_appctx_destroy(isc_appctx_t **ctxp);
+/*!<
+ * \brief Destroy an application context.
+ *
+ * Requires:
+ *\li '*ctxp' is a valid application context.
+ *
+ * Ensures:
+ *\li *ctxp == NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/assertions.h b/lib/isc/include/isc/assertions.h
new file mode 100644
index 0000000..7eafa16
--- /dev/null
+++ b/lib/isc/include/isc/assertions.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file isc/assertions.h
+ */
+
+#pragma once
+
+#include <isc/attributes.h>
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+/*% isc assertion type */
+typedef enum {
+ isc_assertiontype_require,
+ isc_assertiontype_ensure,
+ isc_assertiontype_insist,
+ isc_assertiontype_invariant
+} isc_assertiontype_t;
+
+typedef void (*isc_assertioncallback_t)(const char *, int, isc_assertiontype_t,
+ const char *);
+
+/* coverity[+kill] */
+noreturn void
+isc_assertion_failed(const char *, int, isc_assertiontype_t, const char *);
+
+void isc_assertion_setcallback(isc_assertioncallback_t);
+
+const char *
+isc_assertion_typetotext(isc_assertiontype_t type);
+
+#define ISC_REQUIRE(cond) \
+ ((void)((cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_require, #cond), \
+ 0)))
+
+#define ISC_ENSURE(cond) \
+ ((void)((cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_ensure, #cond), \
+ 0)))
+
+#define ISC_INSIST(cond) \
+ ((void)((cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_insist, #cond), \
+ 0)))
+
+#define ISC_INVARIANT(cond) \
+ ((void)((cond) || \
+ ((isc_assertion_failed)(__FILE__, __LINE__, \
+ isc_assertiontype_invariant, #cond), \
+ 0)))
+
+#define ISC_UNREACHABLE() \
+ (isc_assertion_failed(__FILE__, __LINE__, isc_assertiontype_insist, \
+ "unreachable"), \
+ __builtin_unreachable())
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/astack.h b/lib/isc/include/isc/astack.h
new file mode 100644
index 0000000..a4f6762
--- /dev/null
+++ b/lib/isc/include/isc/astack.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 <inttypes.h>
+
+#include <isc/mem.h>
+#include <isc/types.h>
+
+isc_astack_t *
+isc_astack_new(isc_mem_t *mctx, size_t size);
+/*%<
+ * Allocate and initialize a new array stack of size 'size'.
+ */
+
+void
+isc_astack_destroy(isc_astack_t *stack);
+/*%<
+ * Free an array stack 'stack'.
+ *
+ * Requires:
+ * \li 'stack' is empty.
+ */
+
+bool
+isc_astack_trypush(isc_astack_t *stack, void *obj);
+/*%<
+ * Try to push 'obj' onto array stack 'astack'. On failure, either
+ * because the stack size limit has been reached or because another
+ * thread has already changed the stack pointer, return 'false'.
+ */
+
+void *
+isc_astack_pop(isc_astack_t *stack);
+/*%<
+ * Pop an object off of array stack 'stack'. If the stack is empty,
+ * return NULL.
+ */
diff --git a/lib/isc/include/isc/atomic.h b/lib/isc/include/isc/atomic.h
new file mode 100644
index 0000000..5a415bc
--- /dev/null
+++ b/lib/isc/include/isc/atomic.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#if HAVE_STDATOMIC_H
+#include <stdatomic.h>
+#else
+#include <isc/stdatomic.h>
+#endif
+
+#include <isc/util.h>
+
+/*
+ * We define a few additional macros to make things easier
+ */
+
+/* Relaxed Memory Ordering */
+
+#define atomic_store_relaxed(o, v) \
+ atomic_store_explicit((o), (v), memory_order_relaxed)
+#define atomic_load_relaxed(o) atomic_load_explicit((o), memory_order_relaxed)
+#define atomic_fetch_add_relaxed(o, v) \
+ atomic_fetch_add_explicit((o), (v), memory_order_relaxed)
+#define atomic_fetch_sub_relaxed(o, v) \
+ atomic_fetch_sub_explicit((o), (v), memory_order_relaxed)
+#define atomic_fetch_or_relaxed(o, v) \
+ atomic_fetch_or_explicit((o), (v), memory_order_relaxed)
+#define atomic_fetch_and_relaxed(o, v) \
+ atomic_fetch_and_explicit((o), (v), memory_order_relaxed)
+#define atomic_exchange_relaxed(o, v) \
+ atomic_exchange_explicit((o), (v), memory_order_relaxed)
+#define atomic_compare_exchange_weak_relaxed(o, e, d) \
+ atomic_compare_exchange_weak_explicit( \
+ (o), (e), (d), memory_order_relaxed, memory_order_relaxed)
+#define atomic_compare_exchange_strong_relaxed(o, e, d) \
+ atomic_compare_exchange_strong_explicit( \
+ (o), (e), (d), memory_order_relaxed, memory_order_relaxed)
+#define atomic_compare_exchange_strong_acq_rel(o, e, d) \
+ atomic_compare_exchange_strong_explicit( \
+ (o), (e), (d), memory_order_acq_rel, memory_order_acquire)
+
+/* Acquire-Release Memory Ordering */
+
+#define atomic_store_release(o, v) \
+ atomic_store_explicit((o), (v), memory_order_release)
+#define atomic_load_acquire(o) atomic_load_explicit((o), memory_order_acquire)
+#define atomic_fetch_add_release(o, v) \
+ atomic_fetch_add_explicit((o), (v), memory_order_release)
+#define atomic_fetch_sub_release(o, v) \
+ atomic_fetch_sub_explicit((o), (v), memory_order_release)
+#define atomic_fetch_and_release(o, v) \
+ atomic_fetch_and_explicit((o), (v), memory_order_release)
+#define atomic_fetch_or_release(o, v) \
+ atomic_fetch_or_explicit((o), (v), memory_order_release)
+#define atomic_exchange_acq_rel(o, v) \
+ atomic_exchange_explicit((o), (v), memory_order_acq_rel)
+#define atomic_fetch_sub_acq_rel(o, v) \
+ atomic_fetch_sub_explicit((o), (v), memory_order_acq_rel)
+#define atomic_compare_exchange_weak_acq_rel(o, e, d) \
+ atomic_compare_exchange_weak_explicit( \
+ (o), (e), (d), memory_order_acq_rel, memory_order_acquire)
+#define atomic_compare_exchange_strong_acq_rel(o, e, d) \
+ atomic_compare_exchange_strong_explicit( \
+ (o), (e), (d), memory_order_acq_rel, memory_order_acquire)
+
+/* compare/exchange that MUST succeed */
+#define atomic_compare_exchange_enforced(o, e, d) \
+ RUNTIME_CHECK(atomic_compare_exchange_strong((o), (e), (d)))
diff --git a/lib/isc/include/isc/attributes.h b/lib/isc/include/isc/attributes.h
new file mode 100644
index 0000000..abe6152
--- /dev/null
+++ b/lib/isc/include/isc/attributes.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
+
+#ifdef HAVE_STDNORETURN_H
+#include <stdnoreturn.h>
+#elif HAVE_FUNC_ATTRIBUTE_NORETURN
+#define noreturn __attribute__((noreturn))
+#else
+#define noreturn
+#endif
+
+#if HAVE_FUNC_ATTRIBUTE_RETURNS_NONNULL
+#define ISC_ATTR_RETURNS_NONNULL __attribute__((returns_nonnull))
+#else
+#define ISC_ATTR_RETURNS_NONNULL
+#endif
+
+#ifdef HAVE_FUNC_ATTRIBUTE_MALLOC
+/*
+ * Indicates that a function is malloc-like, i.e., that the
+ * pointer P returned by the function cannot alias any other
+ * pointer valid when the function returns.
+ */
+#define ISC_ATTR_MALLOC __attribute__((malloc))
+#if HAVE_MALLOC_EXT_ATTR
+/*
+ * Associates deallocator as a suitable deallocation function
+ * for pointers returned from the function marked with the attribute.
+ */
+#define ISC_ATTR_DEALLOCATOR(deallocator) __attribute__((malloc(deallocator)))
+/*
+ * Similar to ISC_ATTR_DEALLOCATOR, but allows to speficy an index "idx",
+ * which denotes the positional argument to which when the pointer is passed
+ * in calls to deallocator has the effect of deallocating it.
+ */
+#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx) \
+ __attribute__((malloc(deallocator, idx)))
+/*
+ * Combines both ISC_ATTR_MALLOC and ISC_ATTR_DEALLOCATOR attributes.
+ */
+#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator) \
+ __attribute__((malloc, malloc(deallocator)))
+/*
+ * Similar to ISC_ATTR_MALLOC_DEALLOCATOR, but allows to speficy an index "idx",
+ * which denotes the positional argument to which when the pointer is passed
+ * in calls to deallocator has the effect of deallocating it.
+ */
+#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx) \
+ __attribute__((malloc, malloc(deallocator, idx)))
+#else /* #ifdef HAVE_MALLOC_EXT_ATTR */
+/*
+ * There is support for malloc attribute but not for
+ * extended attributes, so macros that combine attribute malloc
+ * with a deallocator will only expand to malloc attribute.
+ */
+#define ISC_ATTR_DEALLOCATOR(deallocator)
+#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx)
+#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator) ISC_ATTR_MALLOC
+#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx) ISC_ATTR_MALLOC
+#endif
+#else /* #ifdef HAVE_FUNC_ATTRIBUTE_MALLOC */
+/*
+ * There is no support for malloc attribute.
+ */
+#define ISC_ATTR_MALLOC
+#define ISC_ATTR_DEALLOCATOR(deallocator)
+#define ISC_ATTR_DEALLOCATOR_IDX(deallocator, idx)
+#define ISC_ATTR_MALLOC_DEALLOCATOR(deallocator)
+#define ISC_ATTR_MALLOC_DEALLOCATOR_IDX(deallocator, idx)
+#endif /* HAVE_FUNC_ATTRIBUTE_MALLOC */
diff --git a/lib/isc/include/isc/backtrace.h b/lib/isc/include/isc/backtrace.h
new file mode 100644
index 0000000..f818c3b
--- /dev/null
+++ b/lib/isc/include/isc/backtrace.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 isc/backtrace.h
+ * \brief provide a back trace of the running process to help debug problems.
+ *
+ * This module tries to get a back trace of the process using some platform
+ * dependent way when available. It also manages an internal symbol table
+ * that maps function addresses used in the process to their textual symbols.
+ * This module is expected to be used to help debug when some fatal error
+ * happens.
+ *
+ * IMPORTANT NOTE: since the (major) intended use case of this module is
+ * dumping a back trace on a fatal error, normally followed by self termination,
+ * functions defined in this module generally doesn't employ assertion checks
+ * (if it did, a program bug could cause infinite recursive calls to a
+ * backtrace function).
+ */
+
+#pragma once
+
+/***
+ *** Imports
+ ***/
+#include <isc/types.h>
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+int
+isc_backtrace(void **addrs, int maxaddrs);
+/*%<
+ * Get a back trace of the running process above this function itself. On
+ * success, addrs[i] will store the address of the call point of the i-th
+ * stack frame (addrs[0] is the caller of this function). *nframes will store
+ * the total number of frames.
+ *
+ * Requires (note that these are not ensured by assertion checks, see above):
+ *
+ *\li 'addrs' is a valid array containing at least 'maxaddrs' void * entries.
+ *
+ *\li 'nframes' must be non NULL.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_FAILURE
+ *\li #ISC_R_NOTFOUND
+ *\li #ISC_R_NOTIMPLEMENTED
+ */
+
+char **
+isc_backtrace_symbols(void *const *buffer, int size);
+/*
+ * isc_backtrace_symbols() attempts to transform a call stack obtained by
+ * backtrace() into an array of human-readable strings using dladdr(). The
+ * array of strings returned has size elements. It is allocated using
+ * malloc() and should be released using free(). There is no need to free
+ * the individual strings in the array.
+ *
+ * Notes:
+ *
+ *\li On Windows, this is shim implementation using SymFromAddr()
+ *\li On systems with backtrace_symbols(), it's just a thin wrapper
+ *\li Otherwise, it returns NULL
+ *\li See platform NOTES for backtrace_symbols
+ *
+ * Returns:
+ *
+ *\li On success, backtrace_symbols() returns a pointer to the array
+ *\li On error, NULL is returned.
+ */
+
+void
+isc_backtrace_symbols_fd(void *const *buffer, int size, int fd);
+/*
+ * isc_backtrace_symbols_fd() performs the same operation as
+ * isc_backtrace_symbols(), but the resulting strings are immediately written to
+ * the file descriptor fd, and are not returned. isc_backtrace_symbols_fd()
+ * does not call malloc(3), and so can be employed in situations where the
+ * latter function might fail.
+ *
+ * Notes:
+ *
+ *\li See isc_backtrace_symbols() notes
+ *\li See platform NOTES for backtrace_symbols_fd for caveats
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/barrier.h b/lib/isc/include/isc/barrier.h
new file mode 100644
index 0000000..a60472f
--- /dev/null
+++ b/lib/isc/include/isc/barrier.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
+
+#include <isc/util.h>
+
+#if HAVE_PTHREAD_BARRIER_INIT
+
+#include <pthread.h>
+
+#define isc_barrier_t pthread_barrier_t
+
+#define isc_barrier_init(barrier, count) \
+ pthread_barrier_init(barrier, NULL, count)
+#define isc_barrier_destroy(barrier) pthread_barrier_destroy(barrier)
+#define isc_barrier_wait(barrier) pthread_barrier_wait(barrier)
+
+#else
+
+#include <uv.h>
+
+#define isc_barrier_t uv_barrier_t
+
+#define isc_barrier_init(barrier, count) uv_barrier_init(barrier, count)
+#define isc_barrier_destroy(barrier) uv_barrier_destroy(barrier)
+#define isc_barrier_wait(barrier) uv_barrier_wait(barrier)
+
+#endif /* __SANITIZE_THREAD__ */
diff --git a/lib/isc/include/isc/base32.h b/lib/isc/include/isc/base32.h
new file mode 100644
index 0000000..1faa629
--- /dev/null
+++ b/lib/isc/include/isc/base32.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+/*
+ * Routines for manipulating base 32 and base 32 hex encoded data.
+ * Based on RFC 4648.
+ *
+ * Base 32 hex preserves the sort order of data when it is encoded /
+ * decoded.
+ *
+ * Base 32 hex "np" is base 32 hex but no padding is produced or accepted.
+ */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+isc_base32_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target);
+isc_result_t
+isc_base32hex_totext(isc_region_t *source, int wordlength,
+ const char *wordbreak, isc_buffer_t *target);
+isc_result_t
+isc_base32hexnp_totext(isc_region_t *source, int wordlength,
+ const char *wordbreak, isc_buffer_t *target);
+/*!<
+ * \brief Convert data into base32 encoded text.
+ *
+ * Notes:
+ *\li The base32 encoded text in 'target' will be divided into
+ * words of at most 'wordlength' characters, separated by
+ * the 'wordbreak' string. No parentheses will surround
+ * the text.
+ *
+ * Requires:
+ *\li 'source' is a region containing binary data
+ *\li 'target' is a text buffer containing available space
+ *\li 'wordbreak' points to a null-terminated string of
+ * zero or more whitespace characters
+ *
+ * Ensures:
+ *\li target will contain the base32 encoded version of the data
+ * in source. The 'used' pointer in target will be advanced as
+ * necessary.
+ */
+
+isc_result_t
+isc_base32_decodestring(const char *cstr, isc_buffer_t *target);
+isc_result_t
+isc_base32hex_decodestring(const char *cstr, isc_buffer_t *target);
+isc_result_t
+isc_base32hexnp_decodestring(const char *cstr, isc_buffer_t *target);
+/*!<
+ * \brief Decode a null-terminated string in base32, base32hex, or
+ * base32hex non-padded.
+ *
+ * Requires:
+ *\li 'cstr' is non-null.
+ *\li 'target' is a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring'
+ * fit in 'target'.
+ *\li #ISC_R_BADBASE32 -- 'cstr' is not a valid base32 encoding.
+ *
+ * Other error returns are any possible error code from:
+ *\li isc_lex_create(),
+ *\li isc_lex_openbuffer(),
+ *\li isc_base32_tobuffer().
+ */
+
+isc_result_t
+isc_base32_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length);
+isc_result_t
+isc_base32hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length);
+isc_result_t
+isc_base32hexnp_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length);
+/*!<
+ * \brief Convert text encoded in base32, base32hex, or base32hex
+ * non-padded from a lexer context into `target`. If 'length' is
+ * non-negative, it is the expected number of encoded octets to convert.
+ *
+ * If 'length' is -1 then 0 or more encoded octets are expected.
+ * If 'length' is -2 then 1 or more encoded octets are expected.
+ *
+ * Returns:
+ *\li #ISC_R_BADBASE32 -- invalid base32 encoding.
+ *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected
+ * number of encoded octets.
+ *
+ * Requires:
+ *\li 'lexer' is a valid lexer context
+ *\li 'target' is a buffer containing binary data
+ *\li 'length' is -2, -1, or non-negative
+ *
+ * Ensures:
+ *\li target will contain the data represented by the base32 encoded
+ * string parsed by the lexer. No more than `length` octets will
+ * be read, if `length` is non-negative. The 'used' pointer in
+ * 'target' will be advanced as necessary.
+ */
+
+isc_result_t
+isc_base32_decoderegion(isc_region_t *source, isc_buffer_t *target);
+isc_result_t
+isc_base32hex_decoderegion(isc_region_t *source, isc_buffer_t *target);
+isc_result_t
+isc_base32hexnp_decoderegion(isc_region_t *source, isc_buffer_t *target);
+/*!<
+ * \brief Decode a packed (no white space permitted) region in
+ * base32, base32hex or base32hex non-padded.
+ *
+ * Requires:
+ *\li 'source' is a valid region.
+ *\li 'target' is a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring'
+ * fit in 'target'.
+ *\li #ISC_R_BADBASE32 -- 'source' is not a valid base32 encoding.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/base64.h b/lib/isc/include/isc/base64.h
new file mode 100644
index 0000000..b1bc089
--- /dev/null
+++ b/lib/isc/include/isc/base64.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/base64.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+isc_base64_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target);
+/*!<
+ * \brief Convert data into base64 encoded text.
+ *
+ * Notes:
+ *\li The base64 encoded text in 'target' will be divided into
+ * words of at most 'wordlength' characters, separated by
+ * the 'wordbreak' string. No parentheses will surround
+ * the text.
+ *
+ * Requires:
+ *\li 'source' is a region containing binary data
+ *\li 'target' is a text buffer containing available space
+ *\li 'wordbreak' points to a null-terminated string of
+ * zero or more whitespace characters
+ *
+ * Ensures:
+ *\li target will contain the base64 encoded version of the data
+ * in source. The 'used' pointer in target will be advanced as
+ * necessary.
+ */
+
+isc_result_t
+isc_base64_decodestring(const char *cstr, isc_buffer_t *target);
+/*!<
+ * \brief Decode a null-terminated base64 string.
+ *
+ * Requires:
+ *\li 'cstr' is non-null.
+ *\li 'target' is a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring'
+ * fit in 'target'.
+ *\li #ISC_R_BADBASE64 -- 'cstr' is not a valid base64 encoding.
+ *
+ * Other error returns are any possible error code from:
+ *\li isc_lex_create(),
+ *\li isc_lex_openbuffer(),
+ *\li isc_base64_tobuffer().
+ */
+
+isc_result_t
+isc_base64_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length);
+/*!<
+ * \brief Convert base64 encoded text from a lexer context into
+ * `target`. If 'length' is non-negative, it is the expected number of
+ * encoded octets to convert.
+ *
+ * If 'length' is -1 then 0 or more encoded octets are expected.
+ * If 'length' is -2 then 1 or more encoded octets are expected.
+ *
+ * Returns:
+ *\li #ISC_R_BADBASE64 -- invalid base64 encoding.
+ *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected
+ * number of encoded octets.
+ *
+ * Requires:
+ *\li 'lexer' is a valid lexer context
+ *\li 'target' is a buffer containing binary data
+ *\li 'length' is -2, -1, or non-negative
+ *
+ * Ensures:
+ *\li target will contain the data represented by the base64 encoded
+ * string parsed by the lexer. No more than `length` octets will
+ * be read, if `length` is non-negative. The 'used' pointer in
+ * 'target' will be advanced as necessary.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/buffer.h b/lib/isc/include/isc/buffer.h
new file mode 100644
index 0000000..1be0081
--- /dev/null
+++ b/lib/isc/include/isc/buffer.h
@@ -0,0 +1,1023 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/buffer.h
+ *
+ * \brief A buffer is a region of memory, together with a set of related
+ * subregions. Buffers are used for parsing and I/O operations.
+ *
+ * The 'used region' and the 'available region' are disjoint, and their
+ * union is the buffer's region. The used region extends from the beginning
+ * of the buffer region to the last used byte. The available region
+ * extends from one byte greater than the last used byte to the end of the
+ * buffer's region. The size of the used region can be changed using various
+ * buffer commands. Initially, the used region is empty.
+ *
+ * The used region is further subdivided into two disjoint regions: the
+ * 'consumed region' and the 'remaining region'. The union of these two
+ * regions is the used region. The consumed region extends from the beginning
+ * of the used region to the byte before the 'current' offset (if any). The
+ * 'remaining' region extends from the current offset to the end of the used
+ * region. The size of the consumed region can be changed using various
+ * buffer commands. Initially, the consumed region is empty.
+ *
+ * The 'active region' is an (optional) subregion of the remaining region.
+ * It extends from the current offset to an offset in the remaining region
+ * that is selected with isc_buffer_setactive(). Initially, the active region
+ * is empty. If the current offset advances beyond the chosen offset, the
+ * active region will also be empty.
+ *
+ * \verbatim
+ * /------------entire length---------------\
+ * /----- used region -----\/-- available --\
+ * +----------------------------------------+
+ * | consumed | remaining | |
+ * +----------------------------------------+
+ * a b c d e
+ *
+ * a == base of buffer.
+ * b == current pointer. Can be anywhere between a and d.
+ * c == active pointer. Meaningful between b and d.
+ * d == used pointer.
+ * e == length of buffer.
+ *
+ * a-e == entire length of buffer.
+ * a-d == used region.
+ * a-b == consumed region.
+ * b-d == remaining region.
+ * b-c == optional active region.
+ *\endverbatim
+ *
+ * The following invariants are maintained by all routines:
+ *
+ *\code
+ * length > 0
+ *
+ * base is a valid pointer to length bytes of memory
+ *
+ * 0 <= used <= length
+ *
+ * 0 <= current <= used
+ *
+ * 0 <= active <= used
+ * (although active < current implies empty active region)
+ *\endcode
+ *
+ * \li MP:
+ * Buffers have no synchronization. Clients must ensure exclusive
+ * access.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * Memory: 1 pointer + 6 unsigned integers per buffer.
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/assertions.h>
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/list.h>
+#include <isc/magic.h>
+#include <isc/region.h>
+#include <isc/string.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*@{*/
+/*!
+ *** Magic numbers
+ ***/
+#define ISC_BUFFER_MAGIC 0x42756621U /* Buf!. */
+#define ISC_BUFFER_VALID(b) ISC_MAGIC_VALID(b, ISC_BUFFER_MAGIC)
+/*@}*/
+
+/*!
+ * Size granularity for dynamically resizable buffers; when reserving
+ * space in a buffer, we round the allocated buffer length up to the
+ * nearest * multiple of this value.
+ */
+#define ISC_BUFFER_INCR 2048
+
+/*
+ * The following macros MUST be used only on valid buffers. It is the
+ * caller's responsibility to ensure this by using the ISC_BUFFER_VALID
+ * check above, or by calling another isc_buffer_*() function (rather than
+ * another macro.)
+ */
+
+/*@{*/
+/*!
+ * Fundamental buffer elements. (A through E in the introductory comment.)
+ */
+#define isc_buffer_base(b) ((void *)(b)->base) /*a*/
+#define isc_buffer_current(b) \
+ ((void *)((unsigned char *)(b)->base + (b)->current)) /*b*/
+#define isc_buffer_active(b) \
+ ((void *)((unsigned char *)(b)->base + (b)->active)) /*c*/
+#define isc_buffer_used(b) \
+ ((void *)((unsigned char *)(b)->base + (b)->used)) /*d*/
+#define isc_buffer_length(b) ((b)->length) /*e*/
+/*@}*/
+
+/*@{*/
+/*!
+ * Derived lengths. (Described in the introductory comment.)
+ */
+#define isc_buffer_usedlength(b) ((b)->used) /* d-a */
+#define isc_buffer_consumedlength(b) ((b)->current) /* b-a */
+#define isc_buffer_remaininglength(b) ((b)->used - (b)->current) /* d-b */
+#define isc_buffer_activelength(b) ((b)->active - (b)->current) /* c-b */
+#define isc_buffer_availablelength(b) ((b)->length - (b)->used) /* e-d */
+/*@}*/
+
+/*!
+ * Note that the buffer structure is public. This is principally so buffer
+ * operations can be implemented using macros. Applications are strongly
+ * discouraged from directly manipulating the structure.
+ */
+
+struct isc_buffer {
+ unsigned int magic;
+ void *base;
+ /*@{*/
+ /*! The following integers are byte offsets from 'base'. */
+ unsigned int length;
+ unsigned int used;
+ unsigned int current;
+ unsigned int active;
+ /*@}*/
+ /*! linkable */
+ ISC_LINK(isc_buffer_t) link;
+ /*! private internal elements */
+ isc_mem_t *mctx;
+ /* automatically realloc buffer at put* */
+ bool autore;
+};
+
+/***
+ *** Functions
+ ***/
+
+void
+isc_buffer_allocate(isc_mem_t *mctx, isc_buffer_t **dynbuffer,
+ unsigned int length);
+/*!<
+ * \brief Allocate a dynamic linkable buffer which has "length" bytes in the
+ * data region.
+ *
+ * Requires:
+ *\li "mctx" is valid.
+ *
+ *\li "dynbuffer" is non-NULL, and "*dynbuffer" is NULL.
+ *
+ * Note:
+ *\li Changing the buffer's length field is not permitted.
+ */
+
+isc_result_t
+isc_buffer_reserve(isc_buffer_t **dynbuffer, unsigned int size);
+/*!<
+ * \brief Make "size" bytes of space available in the buffer. The buffer
+ * pointer may move when you call this function.
+ *
+ * Requires:
+ *\li "dynbuffer" is not NULL.
+ *
+ *\li "*dynbuffer" is a valid dynamic buffer.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS - success
+ *\li ISC_R_NOMEMORY - no memory available
+ *
+ * Ensures:
+ *\li "*dynbuffer" will be valid on return and will contain all the
+ * original data. However, the buffer pointer may be moved during
+ * reallocation.
+ */
+
+void
+isc_buffer_free(isc_buffer_t **dynbuffer);
+/*!<
+ * \brief Release resources allocated for a dynamic buffer.
+ *
+ * Requires:
+ *\li "dynbuffer" is not NULL.
+ *
+ *\li "*dynbuffer" is a valid dynamic buffer.
+ *
+ * Ensures:
+ *\li "*dynbuffer" will be NULL on return, and all memory associated with
+ * the dynamic buffer is returned to the memory context used in
+ * isc_buffer_allocate().
+ */
+
+void
+isc__buffer_initnull(isc_buffer_t *b);
+
+void
+isc_buffer_reinit(isc_buffer_t *b, void *base, unsigned int length);
+/*!<
+ * \brief Make 'b' refer to the 'length'-byte region starting at base.
+ * Any existing data will be copied.
+ *
+ * Requires:
+ *
+ *\li 'length' > 0 AND length >= previous length
+ *
+ *\li 'base' is a pointer to a sequence of 'length' bytes.
+ *
+ */
+
+void
+isc_buffer_setautorealloc(isc_buffer_t *b, bool enable);
+/*!<
+ * \brief Enable or disable autoreallocation on 'b'.
+ *
+ * Requires:
+ *\li 'b' is a valid dynamic buffer (b->mctx != NULL).
+ *
+ */
+
+void
+isc_buffer_compact(isc_buffer_t *b);
+/*!<
+ * \brief Compact the used region by moving the remaining region so it occurs
+ * at the start of the buffer. The used region is shrunk by the size of
+ * the consumed region, and the consumed region is then made empty.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ * Ensures:
+ *
+ *\li current == 0
+ *
+ *\li The size of the used region is now equal to the size of the remaining
+ * region (as it was before the call). The contents of the used region
+ * are those of the remaining region (as it was before the call).
+ */
+
+uint8_t
+isc_buffer_getuint8(isc_buffer_t *b);
+/*!<
+ * \brief Read an unsigned 8-bit integer from 'b' and return it.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the remaining region of 'b' is at least 1.
+ *
+ * Ensures:
+ *
+ *\li The current pointer in 'b' is advanced by 1.
+ *
+ * Returns:
+ *
+ *\li A 8-bit unsigned integer.
+ */
+
+uint16_t
+isc_buffer_getuint16(isc_buffer_t *b);
+/*!<
+ * \brief Read an unsigned 16-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the remaining region of 'b' is at least 2.
+ *
+ * Ensures:
+ *
+ *\li The current pointer in 'b' is advanced by 2.
+ *
+ * Returns:
+ *
+ *\li A 16-bit unsigned integer.
+ */
+
+uint32_t
+isc_buffer_getuint32(isc_buffer_t *b);
+/*!<
+ * \brief Read an unsigned 32-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the remaining region of 'b' is at least 4.
+ *
+ * Ensures:
+ *
+ *\li The current pointer in 'b' is advanced by 4.
+ *
+ * Returns:
+ *
+ *\li A 32-bit unsigned integer.
+ */
+
+uint64_t
+isc_buffer_getuint48(isc_buffer_t *b);
+/*!<
+ * \brief Read an unsigned 48-bit integer in network byte order from 'b',
+ * convert it to host byte order, and return it.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the remaining region of 'b' is at least 6.
+ *
+ * Ensures:
+ *
+ *\li The current pointer in 'b' is advanced by 6.
+ *
+ * Returns:
+ *
+ *\li A 48-bit unsigned integer (stored in a 64-bit integer).
+ */
+
+void
+isc_buffer_putdecint(isc_buffer_t *b, int64_t v);
+/*!<
+ * \brief Put decimal representation of 'v' in b
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least strlen(dec('v'))
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by strlen(dec('v')).
+ */
+
+isc_result_t
+isc_buffer_copyregion(isc_buffer_t *b, const isc_region_t *r);
+/*!<
+ * \brief Copy the contents of 'r' into 'b'.
+ *
+ * Notes:
+ *\li If 'b' has autoreallocation enabled, and the length of 'r' is greater
+ * than the length of the available region of 'b', 'b' is reallocated.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' is a valid region.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li ISC_R_NOSPACE The available region of 'b' is not
+ * big enough.
+ */
+
+isc_result_t
+isc_buffer_dup(isc_mem_t *mctx, isc_buffer_t **dstp, const isc_buffer_t *src);
+/*!<
+ * \brief Allocate 'dst' and copy used contents of 'src' into it.
+ *
+ * Requires:
+ *\li 'dstp' is not NULL and *dst is NULL.
+ *\li 'src' is a valid buffer.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ */
+
+isc_result_t
+isc_buffer_printf(isc_buffer_t *b, const char *format, ...)
+ ISC_FORMAT_PRINTF(2, 3);
+/*!<
+ * \brief Append a formatted string to the used region of 'b'.
+ *
+ * Notes:
+ *
+ *\li The 'format' argument is a printf(3) string, with additional arguments
+ * as necessary.
+ *
+ *\li If 'b' has autoreallocation enabled, and the length of the formatted
+ * string is greater than the length of the available region of 'b', 'b'
+ * is reallocated.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ * Ensures:
+ *
+ *\li The used pointer in 'b' is advanced by the number of bytes appended
+ * (excluding the terminating NULL byte).
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS Operation succeeded.
+ *\li #ISC_R_NOSPACE 'b' does not allow reallocation and appending the
+ * formatted string to it would cause it to overflow.
+ *\li #ISC_R_NOMEMORY Reallocation failed.
+ *\li #ISC_R_FAILURE Other error occurred.
+ */
+
+/*
+ * Buffer functions implemented as inline.
+ */
+
+/*! \note
+ * XXXDCL Something more could be done with initializing buffers that
+ * point to const data. For example, isc_buffer_constinit() could
+ * set a new boolean flag in the buffer structure indicating whether
+ * the buffer was initialized with that function. Then if the
+ * boolean were true, the isc_buffer_put* functions could assert a
+ * contractual requirement for a non-const buffer.
+ *
+ * One drawback is that the isc_buffer_* functions that return
+ * pointers would still need to return non-const pointers to avoid compiler
+ * warnings, so it would be up to code that uses them to have to deal
+ * with the possibility that the buffer was initialized as const --
+ * a problem that they *already* have to deal with but have absolutely
+ * no ability to. With a new isc_buffer_isconst() function returning
+ * true/false, they could at least assert a contractual requirement for
+ * non-const buffers when needed.
+ */
+
+/*!
+ * \brief Make 'b' refer to the 'length'-byte region starting at 'base'.
+ *
+ * Requires:
+ *
+ *\li 'length' > 0
+ *
+ *\li 'base' is a pointer to a sequence of 'length' bytes.
+ */
+static inline void
+isc_buffer_init(isc_buffer_t *b, void *base, unsigned int length) {
+ ISC_REQUIRE(b != NULL);
+
+ *b = (isc_buffer_t){ .base = base,
+ .length = length,
+ .magic = ISC_BUFFER_MAGIC };
+ ISC_LINK_INIT(b, link);
+}
+
+/*!
+ *\brief Initialize a buffer 'b' with a null data field and zero length.
+ * This can later be grown as needed and swapped in place.
+ */
+static inline void
+isc_buffer_initnull(isc_buffer_t *b) {
+ *b = (isc_buffer_t){ .magic = ISC_BUFFER_MAGIC };
+ ISC_LINK_INIT(b, link);
+}
+
+/*!
+ * \brief Make 'b' refer to the 'length'-byte constant region starting
+ * at 'base'.
+ *
+ * Requires:
+ *
+ *\li 'length' > 0
+ *\li 'base' is a pointer to a sequence of 'length' bytes.
+ */
+#define isc_buffer_constinit(_b, _d, _l) \
+ do { \
+ union { \
+ void *_var; \
+ const void *_const; \
+ } _deconst; \
+ _deconst._const = (_d); \
+ isc_buffer_init((_b), _deconst._var, (_l)); \
+ } while (0)
+
+/*!
+ * \brief Make 'b' an invalid buffer.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ * Ensures:
+ *\li Future attempts to use 'b' without calling isc_buffer_init() on
+ * it will cause an assertion failure.
+ */
+static inline void
+isc_buffer_invalidate(isc_buffer_t *b) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(!ISC_LINK_LINKED(b, link));
+ ISC_REQUIRE(b->mctx == NULL);
+
+ b->magic = 0;
+ b->base = NULL;
+ b->length = 0;
+ b->used = 0;
+ b->current = 0;
+ b->active = 0;
+}
+
+/*!
+ * \brief Make 'r' refer to the region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_region(isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ r->base = b->base;
+ r->length = b->length;
+}
+
+/*!
+ * \brief Make 'r' refer to the used region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_usedregion(const isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ r->base = b->base;
+ r->length = b->used;
+}
+
+/*!
+ * \brief Make 'r' refer to the available region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_availableregion(isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ r->base = isc_buffer_used(b);
+ r->length = isc_buffer_availablelength(b);
+}
+
+/*!
+ * \brief Increase the 'used' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li used + n <= length
+ */
+static inline void
+isc_buffer_add(isc_buffer_t *b, unsigned int n) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(b->used + n <= b->length);
+
+ b->used += n;
+}
+
+/*!
+ * \brief Decrease the 'used' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li used >= n
+ */
+static inline void
+isc_buffer_subtract(isc_buffer_t *b, unsigned int n) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(b->used >= n);
+
+ b->used -= n;
+ if (b->current > b->used) {
+ b->current = b->used;
+ }
+ if (b->active > b->used) {
+ b->active = b->used;
+ }
+}
+
+/*!<
+ * \brief Make the used region empty.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ * Ensures:
+ *
+ *\li used = 0
+ */
+static inline void
+isc_buffer_clear(isc_buffer_t *b) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ b->used = 0;
+ b->current = 0;
+ b->active = 0;
+}
+
+/*!
+ * \brief Make 'r' refer to the consumed region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_consumedregion(isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ r->base = b->base;
+ r->length = b->current;
+}
+
+/*!
+ * \brief Make 'r' refer to the remaining region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_remainingregion(isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ r->base = isc_buffer_current(b);
+ r->length = isc_buffer_remaininglength(b);
+}
+
+/*!
+ * \brief Make 'r' refer to the active region of 'b'.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'r' points to a region structure.
+ */
+static inline void
+isc_buffer_activeregion(isc_buffer_t *b, isc_region_t *r) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(r != NULL);
+
+ if (b->current < b->active) {
+ r->base = isc_buffer_current(b);
+ r->length = isc_buffer_activelength(b);
+ } else {
+ r->base = NULL;
+ r->length = 0;
+ }
+}
+
+/*!
+ * \brief Sets the end of the active region 'n' bytes after current.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer.
+ *
+ *\li current + n <= used
+ */
+static inline void
+isc_buffer_setactive(isc_buffer_t *b, unsigned int n) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(b->current + n <= b->used);
+
+ b->active = b->current + n;
+}
+
+/*!<
+ * \brief Make the consumed region empty.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ * Ensures:
+ *
+ *\li current == 0
+ */
+static inline void
+isc_buffer_first(isc_buffer_t *b) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ b->current = 0;
+}
+
+/*!
+ * \brief Increase the 'consumed' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li current + n <= used
+ */
+static inline void
+isc_buffer_forward(isc_buffer_t *b, unsigned int n) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(b->current + n <= b->used);
+
+ b->current += n;
+}
+
+/*!
+ * \brief Decrease the 'consumed' region of 'b' by 'n' bytes.
+ *
+ * Requires:
+ *
+ *\li 'b' is a valid buffer
+ *
+ *\li n <= current
+ */
+static inline void
+isc_buffer_back(isc_buffer_t *b, unsigned int n) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(n <= b->current);
+
+ b->current -= n;
+}
+
+/*!
+ * \brief Store an unsigned 8-bit integer from 'val' into 'b'.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least 1
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 1.
+ */
+static inline void
+isc_buffer_putuint8(isc_buffer_t *b, uint8_t val) {
+ unsigned char *cp;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, 1) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= 1U);
+
+ cp = isc_buffer_used(b);
+ b->used++;
+ cp[0] = val;
+}
+
+/*!
+ * \brief Store an unsigned 16-bit integer in host byte order from 'val'
+ * into 'b' in network byte order.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least 2
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 2.
+ */
+static inline void
+isc_buffer_putuint16(isc_buffer_t *b, uint16_t val) {
+ unsigned char *cp;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, 2) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= 2U);
+
+ cp = isc_buffer_used(b);
+ b->used += 2;
+ cp[0] = (unsigned char)(val >> 8);
+ cp[1] = (unsigned char)val;
+}
+
+/*!
+ * Store an unsigned 24-bit integer in host byte order from 'val'
+ * into 'b' in network byte order.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least 3
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 3.
+ */
+static inline void
+isc_buffer_putuint24(isc_buffer_t *b, uint32_t val) {
+ unsigned char *cp;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, 3) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= 3U);
+
+ cp = isc_buffer_used(b);
+ b->used += 3;
+ cp[0] = (unsigned char)(val >> 16);
+ cp[1] = (unsigned char)(val >> 8);
+ cp[2] = (unsigned char)val;
+}
+
+/*!
+ * \brief Store an unsigned 32-bit integer in host byte order from 'val'
+ * into 'b' in network byte order.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least 4
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 4.
+ */
+static inline void
+isc_buffer_putuint32(isc_buffer_t *b, uint32_t val) {
+ unsigned char *cp;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, 4) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= 4U);
+
+ cp = isc_buffer_used(b);
+ b->used += 4;
+ cp[0] = (unsigned char)(val >> 24);
+ cp[1] = (unsigned char)(val >> 16);
+ cp[2] = (unsigned char)(val >> 8);
+ cp[3] = (unsigned char)val;
+}
+
+/*!
+ * \brief Store an unsigned 48-bit integer in host byte order from 'val'
+ * into 'b' in network byte order.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li The length of the available region of 'b' is at least 6
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 6.
+ */
+static inline void
+isc_buffer_putuint48(isc_buffer_t *b, uint64_t val) {
+ unsigned char *cp = NULL;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, 6) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= 6U);
+
+ cp = isc_buffer_used(b);
+ b->used += 6;
+ cp[0] = (unsigned char)(val >> 40);
+ cp[1] = (unsigned char)(val >> 32);
+ cp[2] = (unsigned char)(val >> 24);
+ cp[3] = (unsigned char)(val >> 16);
+ cp[4] = (unsigned char)(val >> 8);
+ cp[5] = (unsigned char)val;
+}
+
+/*!
+ * \brief Copy 'length' bytes of memory at 'base' into 'b'.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'base' points to 'length' bytes of valid memory.
+ *
+ *\li The length of the available region of 'b' is at least 'length'
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by 'length'.
+ */
+static inline void
+isc_buffer_putmem(isc_buffer_t *b, const unsigned char *base,
+ unsigned int length) {
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, length) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= (unsigned int)length);
+
+ if (length > 0U) {
+ memmove(isc_buffer_used(b), base, length);
+ b->used += length;
+ }
+}
+
+/*!
+ * \brief Copy 'source' into 'b', not including terminating NUL.
+ *
+ * Requires:
+ *\li 'b' is a valid buffer.
+ *
+ *\li 'source' is a valid NULL terminated string.
+ *
+ *\li The length of the available region of 'b' is at least strlen('source')
+ * or the buffer has autoreallocation enabled.
+ *
+ * Ensures:
+ *\li The used pointer in 'b' is advanced by strlen('source').
+ */
+static inline void
+isc_buffer_putstr(isc_buffer_t *b, const char *source) {
+ unsigned int length;
+ unsigned char *cp;
+
+ ISC_REQUIRE(ISC_BUFFER_VALID(b));
+ ISC_REQUIRE(source != NULL);
+
+ length = (unsigned int)strlen(source);
+ if (b->autore) {
+ isc_buffer_t *tmp = b;
+ ISC_REQUIRE(isc_buffer_reserve(&tmp, length) == ISC_R_SUCCESS);
+ }
+
+ ISC_REQUIRE(isc_buffer_availablelength(b) >= length);
+
+ cp = isc_buffer_used(b);
+ memmove(cp, source, length);
+ b->used += length;
+}
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/cmocka.h b/lib/isc/include/isc/cmocka.h
new file mode 100644
index 0000000..de86d5a
--- /dev/null
+++ b/lib/isc/include/isc/cmocka.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 isc/cmocka.h */
+
+#pragma once
+
+#include <cmocka.h>
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Copy the test identified by 'name' from 'tests' to 'selected'.
+ */
+#define cmocka_add_test_byname(tests, name, selected) \
+ _cmocka_add_test_byname(tests, sizeof(tests) / sizeof(tests[0]), name, \
+ selected, \
+ sizeof(selected) / sizeof(selected[0]))
+
+static inline bool
+_cmocka_add_test_byname(const struct CMUnitTest *tests, size_t ntests,
+ const char *name, struct CMUnitTest *selected,
+ size_t nselected) {
+ size_t i, j;
+
+ for (i = 0; i < ntests && tests[i].name != NULL; i++) {
+ if (strcmp(tests[i].name, name) != 0) {
+ continue;
+ }
+ for (j = 0; j < nselected && selected[j].name != NULL; j++) {
+ if (strcmp(tests[j].name, name) == 0) {
+ break;
+ }
+ }
+ if (j < nselected && selected[j].name == NULL) {
+ selected[j] = tests[i];
+ }
+ return (true);
+ }
+ return (false);
+}
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/commandline.h b/lib/isc/include/isc/commandline.h
new file mode 100644
index 0000000..ac72135
--- /dev/null
+++ b/lib/isc/include/isc/commandline.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/commandline.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+/*% Index into parent argv vector. */
+extern int isc_commandline_index;
+/*% Character checked for validity. */
+extern int isc_commandline_option;
+/*% Argument associated with option. */
+extern char *isc_commandline_argument;
+/*% For printing error messages. */
+extern char *isc_commandline_progname;
+/*% Print error message. */
+extern bool isc_commandline_errprint;
+/*% Reset getopt. */
+extern bool isc_commandline_reset;
+
+ISC_LANG_BEGINDECLS
+
+int
+isc_commandline_parse(int argc, char *const *argv, const char *options);
+/*%<
+ * Parse a command line (similar to getopt())
+ */
+
+isc_result_t
+isc_commandline_strtoargv(isc_mem_t *mctx, char *s, unsigned int *argcp,
+ char ***argvp, unsigned int n);
+/*%<
+ * Tokenize the string "s" into whitespace-separated words,
+ * returning 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_free(). The string
+ * is modified in-place.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/condition.h b/lib/isc/include/isc/condition.h
new file mode 100644
index 0000000..0e1dcf1
--- /dev/null
+++ b/lib/isc/include/isc/condition.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <errno.h>
+
+#include <isc/error.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/result.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/types.h>
+
+typedef pthread_cond_t isc_condition_t;
+
+#define isc_condition_init(cond) \
+ if (pthread_cond_init(cond, NULL) != 0) { \
+ FATAL_SYSERROR(errno, "pthread_cond_init()"); \
+ }
+
+#define isc_condition_wait(cp, mp) \
+ ((pthread_cond_wait((cp), (mp)) == 0) ? ISC_R_SUCCESS \
+ : ISC_R_UNEXPECTED)
+
+#define isc_condition_signal(cp) \
+ ((pthread_cond_signal((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+
+#define isc_condition_broadcast(cp) \
+ ((pthread_cond_broadcast((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+
+#define isc_condition_destroy(cp) \
+ ((pthread_cond_destroy((cp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_condition_waituntil(isc_condition_t *, isc_mutex_t *, isc_time_t *);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/counter.h b/lib/isc/include/isc/counter.h
new file mode 100644
index 0000000..820e4a2
--- /dev/null
+++ b/lib/isc/include/isc/counter.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/counter.h
+ *
+ * \brief The isc_counter_t object is a simplified version of the
+ * isc_quota_t object; it tracks the consumption of limited
+ * resources, returning an error condition when the quota is
+ * exceeded. However, unlike isc_quota_t, attaching and detaching
+ * from a counter object does not increment or decrement the counter.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+
+/*****
+***** Types.
+*****/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_counter_create(isc_mem_t *mctx, int limit, isc_counter_t **counterp);
+/*%<
+ * Allocate and initialize a counter object.
+ */
+
+isc_result_t
+isc_counter_increment(isc_counter_t *counter);
+/*%<
+ * Increment the counter.
+ *
+ * If the counter limit is nonzero and has been reached, then
+ * return ISC_R_QUOTA, otherwise ISC_R_SUCCESS. (The counter is
+ * incremented regardless of return value.)
+ */
+
+unsigned int
+isc_counter_used(isc_counter_t *counter);
+/*%<
+ * Return the current counter value.
+ */
+
+void
+isc_counter_setlimit(isc_counter_t *counter, int limit);
+/*%<
+ * Set the counter limit.
+ */
+
+void
+isc_counter_attach(isc_counter_t *source, isc_counter_t **targetp);
+/*%<
+ * Attach to a counter object, increasing its reference counter.
+ */
+
+void
+isc_counter_detach(isc_counter_t **counterp);
+/*%<
+ * Detach (and destroy if reference counter has dropped to zero)
+ * a counter object.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/crc64.h b/lib/isc/include/isc/crc64.h
new file mode 100644
index 0000000..4147672
--- /dev/null
+++ b/lib/isc/include/isc/crc64.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/crc64.h
+ * \brief CRC64 in C
+ */
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_crc64_init(uint64_t *crc);
+/*%
+ * Initialize a new CRC.
+ *
+ * Requires:
+ * * 'crc' is not NULL.
+ */
+
+void
+isc_crc64_update(uint64_t *crc, const void *data, size_t len);
+/*%
+ * Add data to the CRC.
+ *
+ * Requires:
+ * * 'crc' is not NULL.
+ * * 'data' is not NULL.
+ */
+
+void
+isc_crc64_final(uint64_t *crc);
+/*%
+ * Finalize the CRC.
+ *
+ * Requires:
+ * * 'crc' is not NULL.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/deprecated.h b/lib/isc/include/isc/deprecated.h
new file mode 100644
index 0000000..c83508d
--- /dev/null
+++ b/lib/isc/include/isc/deprecated.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
+
+#if (__GNUC__ + 0) > 3
+#define ISC_DEPRECATED __attribute__((deprecated))
+#else /* if (__GNUC__ + 0) > 3 */
+#define ISC_DEPRECATED /* none */
+#endif /* __GNUC__ > 3*/
diff --git a/lib/isc/include/isc/dir.h b/lib/isc/include/isc/dir.h
new file mode 100644
index 0000000..c1ff2c7
--- /dev/null
+++ b/lib/isc/include/isc/dir.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <dirent.h>
+#include <limits.h>
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+#include <sys/types.h> /* Required on some systems. */
+
+#ifndef NAME_MAX
+#define NAME_MAX 256
+#endif
+
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
+/*% Directory Entry */
+typedef struct isc_direntry {
+ char name[NAME_MAX];
+ unsigned int length;
+} isc_direntry_t;
+
+/*% Directory */
+typedef struct isc_dir {
+ unsigned int magic;
+ char dirname[PATH_MAX];
+ isc_direntry_t entry;
+ DIR *handle;
+} isc_dir_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_dir_init(isc_dir_t *dir);
+
+isc_result_t
+isc_dir_open(isc_dir_t *dir, const char *dirname);
+
+isc_result_t
+isc_dir_read(isc_dir_t *dir);
+
+isc_result_t
+isc_dir_reset(isc_dir_t *dir);
+
+void
+isc_dir_close(isc_dir_t *dir);
+
+isc_result_t
+isc_dir_chdir(const char *dirname);
+
+isc_result_t
+isc_dir_chroot(const char *dirname);
+
+isc_result_t
+isc_dir_createunique(char *templet);
+/*!<
+ * Use a templet (such as from isc_file_mktemplate()) to create a uniquely
+ * named, empty directory. The templet string is modified in place.
+ * If result == ISC_R_SUCCESS, it is the name of the directory that was
+ * created.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/endian.h b/lib/isc/include/isc/endian.h
new file mode 100644
index 0000000..be91b1d
--- /dev/null
+++ b/lib/isc/include/isc/endian.h
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \
+ defined(__OpenBSD__) || defined(__bsdi__)
+
+#include <sys/endian.h>
+
+/*
+ * Recent BSDs should have [bl]e{16,32,64}toh() defined in <sys/endian.h>.
+ * Older ones might not, but these should have the alternatively named
+ * [bl]etoh{16,32,64}() functions defined.
+ */
+#ifndef be16toh
+#define be16toh(x) betoh16(x)
+#define le16toh(x) letoh16(x)
+#define be32toh(x) betoh32(x)
+#define le32toh(x) letoh32(x)
+#define be64toh(x) betoh64(x)
+#define le64toh(x) letoh64(x)
+#endif /* !be16toh */
+
+#elif defined __APPLE__
+
+/*
+ * macOS has its own byte-swapping routines, so use these.
+ */
+
+#include <libkern/OSByteOrder.h>
+
+#define htobe16(x) OSSwapHostToBigInt16(x)
+#define htole16(x) OSSwapHostToLittleInt16(x)
+#define be16toh(x) OSSwapBigToHostInt16(x)
+#define le16toh(x) OSSwapLittleToHostInt16(x)
+
+#define htobe32(x) OSSwapHostToBigInt32(x)
+#define htole32(x) OSSwapHostToLittleInt32(x)
+#define be32toh(x) OSSwapBigToHostInt32(x)
+#define le32toh(x) OSSwapLittleToHostInt32(x)
+
+#define htobe64(x) OSSwapHostToBigInt64(x)
+#define htole64(x) OSSwapHostToLittleInt64(x)
+#define be64toh(x) OSSwapBigToHostInt64(x)
+#define le64toh(x) OSSwapLittleToHostInt64(x)
+
+#elif defined(sun) || defined(__sun) || defined(__SVR4)
+
+/*
+ * For Solaris, rely on the fallback definitions below, though use
+ * Solaris-specific versions of bswap_{16,32,64}().
+ */
+
+#include <sys/byteorder.h>
+
+#define bswap_16(x) BSWAP_16(x)
+#define bswap_32(x) BSWAP_32(x)
+#define bswap_64(x) BSWAP_64(x)
+
+#elif defined(__ANDROID__) || defined(__CYGWIN__) || defined(__GNUC__) || \
+ defined(__GNU__)
+
+#include <byteswap.h>
+#include <endian.h>
+
+#else /* if defined(__DragonFly__) || defined(__FreeBSD__) || \
+ * defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) */
+
+#endif /* Specific platform support */
+
+/*
+ * Fallback definitions.
+ */
+
+#include <inttypes.h>
+
+#ifndef bswap_16
+#define bswap_16(x) \
+ ((uint16_t)((((uint16_t)(x)&0xff00) >> 8) | \
+ (((uint16_t)(x)&0x00ff) << 8)))
+#endif /* !bswap_16 */
+
+#ifndef bswap_32
+#define bswap_32(x) \
+ ((uint32_t)((((uint32_t)(x)&0xff000000) >> 24) | \
+ (((uint32_t)(x)&0x00ff0000) >> 8) | \
+ (((uint32_t)(x)&0x0000ff00) << 8) | \
+ (((uint32_t)(x)&0x000000ff) << 24)))
+#endif /* !bswap_32 */
+
+#ifndef bswap_64
+#define bswap_64(x) \
+ ((uint64_t)((((uint64_t)(x)&0xff00000000000000ULL) >> 56) | \
+ (((uint64_t)(x)&0x00ff000000000000ULL) >> 40) | \
+ (((uint64_t)(x)&0x0000ff0000000000ULL) >> 24) | \
+ (((uint64_t)(x)&0x000000ff00000000ULL) >> 8) | \
+ (((uint64_t)(x)&0x00000000ff000000ULL) << 8) | \
+ (((uint64_t)(x)&0x0000000000ff0000ULL) << 24) | \
+ (((uint64_t)(x)&0x000000000000ff00ULL) << 40) | \
+ (((uint64_t)(x)&0x00000000000000ffULL) << 56)))
+#endif /* !bswap_64 */
+
+#ifndef htobe16
+#if WORDS_BIGENDIAN
+
+#define htobe16(x) (x)
+#define htole16(x) bswap_16(x)
+#define be16toh(x) (x)
+#define le16toh(x) bswap_16(x)
+
+#else /* WORDS_BIGENDIAN */
+
+#define htobe16(x) bswap_16(x)
+#define htole16(x) (x)
+#define be16toh(x) bswap_16(x)
+#define le16toh(x) (x)
+
+#endif /* WORDS_BIGENDIAN */
+#endif /* !htobe16 */
+
+#ifndef htobe32
+#if WORDS_BIGENDIAN
+
+#define htobe32(x) (x)
+#define htole32(x) bswap_32(x)
+#define be32toh(x) (x)
+#define le32toh(x) bswap_32(x)
+
+#else /* WORDS_BIGENDIAN */
+
+#define htobe32(x) bswap_32(x)
+#define htole32(x) (x)
+#define be32toh(x) bswap_32(x)
+#define le32toh(x) (x)
+
+#endif /* WORDS_BIGENDIAN */
+#endif /* !htobe32 */
+
+#ifndef htobe64
+#if WORDS_BIGENDIAN
+
+#define htobe64(x) (x)
+#define htole64(x) bswap_64(x)
+#define be64toh(x) (x)
+#define le64toh(x) bswap_64(x)
+
+#else /* WORDS_BIGENDIAN */
+
+#define htobe64(x) bswap_64(x)
+#define htole64(x) (x)
+#define be64toh(x) bswap_64(x)
+#define le64toh(x) (x)
+
+#endif /* WORDS_BIGENDIAN */
+#endif /* !htobe64 */
diff --git a/lib/isc/include/isc/errno.h b/lib/isc/include/isc/errno.h
new file mode 100644
index 0000000..0151df3
--- /dev/null
+++ b/lib/isc/include/isc/errno.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
+
+/*! \file isc/file.h */
+
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_errno_toresult(int err);
+/*!<
+ * \brief Convert a POSIX errno value to an ISC result code.
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/error.h b/lib/isc/include/isc/error.h
new file mode 100644
index 0000000..f4f6682
--- /dev/null
+++ b/lib/isc/include/isc/error.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 isc/error.h */
+
+#include <stdarg.h>
+
+#include <isc/attributes.h>
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef void (*isc_errorcallback_t)(const char *, int, const char *,
+ const char *, va_list);
+
+/*% set unexpected error */
+void isc_error_setunexpected(isc_errorcallback_t);
+
+/*% set fatal error */
+void isc_error_setfatal(isc_errorcallback_t);
+
+/*% unexpected error */
+void
+isc_error_unexpected(const char *, int, const char *, const char *, ...)
+ ISC_FORMAT_PRINTF(4, 5);
+
+/*% fatal error */
+noreturn void
+isc_error_fatal(const char *, int, const char *, const char *, ...)
+ ISC_FORMAT_PRINTF(4, 5);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/event.h b/lib/isc/include/isc/event.h
new file mode 100644
index 0000000..811bcd8
--- /dev/null
+++ b/lib/isc/include/isc/event.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/event.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*****
+***** Events.
+*****/
+
+typedef void (*isc_eventdestructor_t)(isc_event_t *);
+
+#define ISC_EVENT_COMMON(ltype) \
+ size_t ev_size; \
+ unsigned int ev_attributes; \
+ void *ev_tag; \
+ isc_eventtype_t ev_type; \
+ isc_taskaction_t ev_action; \
+ void *ev_arg; \
+ void *ev_sender; \
+ isc_eventdestructor_t ev_destroy; \
+ void *ev_destroy_arg; \
+ ISC_LINK(ltype) ev_link; \
+ ISC_LINK(ltype) ev_ratelink
+
+/*%
+ * Attributes matching a mask of 0x000000ff are reserved for the task library's
+ * definition. Attributes of 0xffffff00 may be used by the application
+ * or non-ISC libraries.
+ */
+#define ISC_EVENTATTR_NOPURGE 0x00000001
+
+/*%
+ * The ISC_EVENTATTR_CANCELED attribute is intended to indicate
+ * that an event is delivered as a result of a canceled operation
+ * rather than successful completion, by mutual agreement
+ * between the sender and receiver. It is not set or used by
+ * the task system.
+ */
+#define ISC_EVENTATTR_CANCELED 0x00000002
+
+#define ISC_EVENT_INIT(event, sz, at, ta, ty, ac, ar, sn, df, da) \
+ do { \
+ (event)->ev_size = (sz); \
+ (event)->ev_attributes = (at); \
+ (event)->ev_tag = (ta); \
+ (event)->ev_type = (ty); \
+ (event)->ev_action = (ac); \
+ (event)->ev_arg = (ar); \
+ (event)->ev_sender = (sn); \
+ (event)->ev_destroy = (df); \
+ (event)->ev_destroy_arg = (da); \
+ ISC_LINK_INIT((event), ev_link); \
+ ISC_LINK_INIT((event), ev_ratelink); \
+ } while (0)
+
+/*%
+ * This structure is public because "subclassing" it may be useful when
+ * defining new event types.
+ */
+struct isc_event {
+ ISC_EVENT_COMMON(struct isc_event);
+};
+
+#define ISC_EVENTTYPE_FIRSTEVENT 0x00000000
+#define ISC_EVENTTYPE_LASTEVENT 0xffffffff
+
+#define ISC_EVENT_PTR(p) ((isc_event_t **)(void *)(p))
+
+ISC_LANG_BEGINDECLS
+
+isc_event_t *
+isc_event_allocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
+ isc_taskaction_t action, void *arg, size_t size);
+isc_event_t *
+isc_event_constallocate(isc_mem_t *mctx, void *sender, isc_eventtype_t type,
+ isc_taskaction_t action, const void *arg, size_t size);
+/*%<
+ * Allocate an event structure.
+ *
+ * Allocate and initialize in a structure with initial elements
+ * defined by:
+ *
+ * \code
+ * struct {
+ * ISC_EVENT_COMMON(struct isc_event);
+ * ...
+ * };
+ * \endcode
+ *
+ * Requires:
+ *\li 'size' >= sizeof(struct isc_event)
+ *\li 'action' to be non NULL
+ *
+ * Returns:
+ *\li a pointer to a initialized structure of the requested size.
+ *\li NULL if unable to allocate memory.
+ */
+
+void
+isc_event_free(isc_event_t **);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/eventclass.h b/lib/isc/include/isc/eventclass.h
new file mode 100644
index 0000000..b8be2ff
--- /dev/null
+++ b/lib/isc/include/isc/eventclass.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 isc/eventclass.h
+ ***** Registry of Predefined Event Type Classes
+ *****/
+
+/*%
+ * An event class is an unsigned 16 bit number. Each class may contain up
+ * to 65536 events. An event type is formed by adding the event number
+ * within the class to the class number.
+ *
+ */
+
+#define ISC_EVENTCLASS(eclass) ((eclass) << 16)
+
+/*@{*/
+/*!
+ * Classes < 1024 are reserved for ISC use.
+ * Event classes >= 1024 and <= 65535 are reserved for application use.
+ */
+
+#define ISC_EVENTCLASS_TASK ISC_EVENTCLASS(0)
+#define ISC_EVENTCLASS_TIMER ISC_EVENTCLASS(1)
+#define ISC_EVENTCLASS_SOCKET ISC_EVENTCLASS(2)
+#define ISC_EVENTCLASS_FILE ISC_EVENTCLASS(3)
+#define ISC_EVENTCLASS_DNS ISC_EVENTCLASS(4)
+#define ISC_EVENTCLASS_APP ISC_EVENTCLASS(5)
+#define ISC_EVENTCLASS_OMAPI ISC_EVENTCLASS(6)
+#define ISC_EVENTCLASS_RATELIMITER ISC_EVENTCLASS(7)
+#define ISC_EVENTCLASS_ISCCC ISC_EVENTCLASS(8)
+#define ISC_EVENTCLASS_NS ISC_EVENTCLASS(9)
+/*@}*/
diff --git a/lib/isc/include/isc/file.h b/lib/isc/include/isc/file.h
new file mode 100644
index 0000000..f78c5c5
--- /dev/null
+++ b/lib/isc/include/isc/file.h
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/file.h */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/stat.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_file_settime(const char *file, isc_time_t *time);
+
+isc_result_t
+isc_file_mode(const char *file, mode_t *modep);
+
+isc_result_t
+isc_file_getmodtime(const char *file, isc_time_t *time);
+/*!<
+ * \brief Get the time of last modification of a file.
+ *
+ * Notes:
+ *\li The time that is set is relative to the (OS-specific) epoch, as are
+ * all isc_time_t structures.
+ *
+ * Requires:
+ *\li file != NULL.
+ *\li time != NULL.
+ *
+ * Ensures:
+ *\li If the file could not be accessed, 'time' is unchanged.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * Success.
+ *\li #ISC_R_NOTFOUND
+ * No such file exists.
+ *\li #ISC_R_INVALIDFILE
+ * The path specified was not usable by the operating system.
+ *\li #ISC_R_NOPERM
+ * The file's metainformation could not be retrieved because
+ * permission was denied to some part of the file's path.
+ *\li #ISC_R_IOERROR
+ * Hardware error interacting with the filesystem.
+ *\li #ISC_R_UNEXPECTED
+ * Something totally unexpected happened.
+ *
+ */
+
+isc_result_t
+isc_file_mktemplate(const char *path, char *buf, size_t buflen);
+/*!<
+ * \brief Generate a template string suitable for use with
+ * isc_file_openunique().
+ *
+ * Notes:
+ *\li This function is intended to make creating temporary files
+ * portable between different operating systems.
+ *
+ *\li The path is prepended to an implementation-defined string and
+ * placed into buf. The string has no path characters in it,
+ * and its maximum length is 14 characters plus a NUL. Thus
+ * buflen should be at least strlen(path) + 15 characters or
+ * an error will be returned.
+ *
+ * Requires:
+ *\li buf != NULL.
+ *
+ * Ensures:
+ *\li If result == #ISC_R_SUCCESS:
+ * buf contains a string suitable for use as the template argument
+ * to isc_file_openunique().
+ *
+ *\li If result != #ISC_R_SUCCESS:
+ * buf is unchanged.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOSPACE buflen indicates buf is too small for the catenation
+ * of the path with the internal template string.
+ */
+
+isc_result_t
+isc_file_openunique(char *templet, FILE **fp);
+isc_result_t
+isc_file_openuniqueprivate(char *templet, FILE **fp);
+isc_result_t
+isc_file_openuniquemode(char *templet, int mode, FILE **fp);
+isc_result_t
+isc_file_bopenunique(char *templet, FILE **fp);
+isc_result_t
+isc_file_bopenuniqueprivate(char *templet, FILE **fp);
+isc_result_t
+isc_file_bopenuniquemode(char *templet, int mode, FILE **fp);
+/*!<
+ * \brief Create and open a file with a unique name based on 'templet'.
+ * isc_file_bopen*() open the file in binary mode in Windows.
+ * isc_file_open*() open the file in text mode in Windows.
+ *
+ * Notes:
+ *\li 'template' is a reserved work in C++. If you want to complain
+ * about the spelling of 'templet', first look it up in the
+ * Merriam-Webster English dictionary. (http://www.m-w.com/)
+ *
+ *\li This function works by using the template to generate file names.
+ * The template must be a writable string, as it is modified in place.
+ * Trailing X characters in the file name (full file name on Unix,
+ * basename on Win32 -- eg, tmp-XXXXXX vs XXXXXX.tmp, respectively)
+ * are replaced with ASCII characters until a non-existent filename
+ * is found. If the template does not include pathname information,
+ * the files in the working directory of the program are searched.
+ *
+ *\li isc_file_mktemplate is a good, portable way to get a template.
+ *
+ * Requires:
+ *\li 'fp' is non-NULL and '*fp' is NULL.
+ *
+ *\li 'template' is non-NULL, and of a form suitable for use by
+ * the system as described above.
+ *
+ * Ensures:
+ *\li If result is #ISC_R_SUCCESS:
+ * *fp points to an stream opening in stdio's "w+" mode.
+ *
+ *\li If result is not #ISC_R_SUCCESS:
+ * *fp is NULL.
+ *
+ * No file is open. Even if one was created (but unable
+ * to be reopened as a stdio FILE pointer) then it has been
+ * removed.
+ *
+ *\li This function does *not* ensure that the template string has not been
+ * modified, even if the operation was unsuccessful.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * Success.
+ *\li #ISC_R_EXISTS
+ * No file with a unique name could be created based on the
+ * template.
+ *\li #ISC_R_INVALIDFILE
+ * The path specified was not usable by the operating system.
+ *\li #ISC_R_NOPERM
+ * The file could not be created because permission was denied
+ * to some part of the file's path.
+ *\li #ISC_R_IOERROR
+ * Hardware error interacting with the filesystem.
+ *\li #ISC_R_UNEXPECTED
+ * Something totally unexpected happened.
+ */
+
+isc_result_t
+isc_file_remove(const char *filename);
+/*!<
+ * \brief Remove the file named by 'filename'.
+ */
+
+isc_result_t
+isc_file_rename(const char *oldname, const char *newname);
+/*!<
+ * \brief Rename the file 'oldname' to 'newname'.
+ */
+
+bool
+isc_file_exists(const char *pathname);
+/*!<
+ * \brief Return #true if the calling process can tell that the given file
+ * exists. Will not return true if the calling process has insufficient
+ * privileges to search the entire path.
+ */
+
+bool
+isc_file_isabsolute(const char *filename);
+/*!<
+ * \brief Return #true if the given file name is absolute.
+ */
+
+isc_result_t
+isc_file_isplainfile(const char *name);
+
+isc_result_t
+isc_file_isplainfilefd(int fd);
+/*!<
+ * \brief Check that the file is a plain file
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * Success. The file is a plain file.
+ *\li #ISC_R_INVALIDFILE
+ * The path specified was not usable by the operating system.
+ *\li #ISC_R_FILENOTFOUND
+ * The file does not exist. This return code comes from
+ * errno=ENOENT when stat returns -1. This code is mentioned
+ * here, because in logconf.c, it is the one rcode that is
+ * permitted in addition to ISC_R_SUCCESS. This is done since
+ * the next call in logconf.c is to isc_stdio_open(), which
+ * will create the file if it can.
+ *\li other ISC_R_* errors translated from errno
+ * These occur when stat returns -1 and an errno.
+ */
+
+isc_result_t
+isc_file_isdirectory(const char *name);
+/*!<
+ * \brief Check that 'name' exists and is a directory.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * Success, file is a directory.
+ *\li #ISC_R_INVALIDFILE
+ * File is not a directory.
+ *\li #ISC_R_FILENOTFOUND
+ * File does not exist.
+ *\li other ISC_R_* errors translated from errno
+ * These occur when stat returns -1 and an errno.
+ */
+
+bool
+isc_file_iscurrentdir(const char *filename);
+/*!<
+ * \brief Return #true if the given file name is the current directory (".").
+ */
+
+bool
+isc_file_ischdiridempotent(const char *filename);
+/*%<
+ * Return #true if calling chdir(filename) multiple times will give
+ * the same result as calling it once.
+ */
+
+const char *
+isc_file_basename(const char *filename);
+/*%<
+ * Return the final component of the path in the file name.
+ */
+
+isc_result_t
+isc_file_progname(const char *filename, char *buf, size_t buflen);
+/*!<
+ * \brief Given an operating system specific file name "filename"
+ * referring to a program, return the canonical program name.
+ *
+ * Any directory prefix or executable file name extension (if
+ * used on the OS in case) is stripped. On systems where program
+ * names are case insensitive, the name is canonicalized to all
+ * lower case. The name is written to 'buf', an array of 'buflen'
+ * chars, and null terminated.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE The name did not fit in 'buf'.
+ */
+
+isc_result_t
+isc_file_template(const char *path, const char *templet, char *buf,
+ size_t buflen);
+/*%<
+ * Create an OS specific template using 'path' to define the directory
+ * 'templet' to describe the filename and store the result in 'buf'
+ * such that path can be renamed to buf atomically.
+ */
+
+isc_result_t
+isc_file_renameunique(const char *file, char *templet);
+/*%<
+ * Rename 'file' using 'templet' as a template for the new file name.
+ */
+
+isc_result_t
+isc_file_absolutepath(const char *filename, char *path, size_t pathlen);
+/*%<
+ * Given a file name, return the fully qualified path to the file.
+ */
+
+/*
+ * XXX We should also have a isc_file_writeeopen() function
+ * for safely open a file in a publicly writable directory
+ * (see write_open() in BIND 8's ns_config.c).
+ */
+
+isc_result_t
+isc_file_truncate(const char *filename, isc_offset_t size);
+/*%<
+ * Truncate/extend the file specified to 'size' bytes.
+ */
+
+isc_result_t
+isc_file_safecreate(const char *filename, FILE **fp);
+/*%<
+ * Open 'filename' for writing, truncating if necessary. Ensure that
+ * if it existed it was a normal file. If creating the file, ensure
+ * that only the owner can read/write it.
+ */
+
+isc_result_t
+isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
+ char const **basename);
+/*%<
+ * Split a path into dirname and basename. If 'path' contains no slash
+ * (or, on windows, backslash), then '*dirname' is set to ".".
+ *
+ * Allocates memory for '*dirname', which can be freed with isc_mem_free().
+ *
+ * Returns:
+ * - ISC_R_SUCCESS on success
+ * - ISC_R_INVALIDFILE if 'path' is empty or ends with '/'
+ * - ISC_R_NOMEMORY if unable to allocate memory
+ */
+
+isc_result_t
+isc_file_getsize(const char *file, off_t *size);
+/*%<
+ * Return the size of the file (stored in the parameter pointed
+ * to by 'size') in bytes.
+ *
+ * Returns:
+ * - ISC_R_SUCCESS on success
+ */
+
+isc_result_t
+isc_file_getsizefd(int fd, off_t *size);
+/*%<
+ * Return the size of the file (stored in the parameter pointed
+ * to by 'size') in bytes.
+ *
+ * Returns:
+ * - ISC_R_SUCCESS on success
+ */
+
+isc_result_t
+isc_file_sanitize(const char *dir, const char *base, const char *ext,
+ char *path, size_t length);
+/*%<
+ * Generate a sanitized filename, such as for MKEYS or NZF files.
+ *
+ * Historically, MKEYS and NZF files used SHA256 hashes of the view
+ * name for the filename; this was to deal with the possibility of
+ * forbidden characters such as "/" being in a view name, and to
+ * avoid problems with case-insensitive file systems.
+ *
+ * Given a basename 'base' and an extension 'ext', this function checks
+ * for the existence of file using the old-style name format in directory
+ * 'dir'. If found, it returns the path to that file. If there is no
+ * file already in place, a new pathname is generated; if the basename
+ * contains any excluded characters, then a truncated SHA256 hash is
+ * used, otherwise the basename is used. The path name is copied
+ * into 'path', which must point to a buffer of at least 'length'
+ * bytes.
+ *
+ * Requires:
+ * - base != NULL
+ * - path != NULL
+ *
+ * Returns:
+ * - ISC_R_SUCCESS on success
+ * - ISC_R_NOSPACE if the resulting path would be longer than 'length'
+ */
+
+bool
+isc_file_isdirwritable(const char *path);
+/*%<
+ * Return true if the path is a directory and is writable
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/formatcheck.h b/lib/isc/include/isc/formatcheck.h
new file mode 100644
index 0000000..e3a37b0
--- /dev/null
+++ b/lib/isc/include/isc/formatcheck.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
+
+/*! \file isc/formatcheck.h */
+
+/*%
+ * ISC_FORMAT_PRINTF().
+ *
+ * \li fmt is the location of the format string parameter.
+ * \li args is the location of the first argument (or 0 for no argument
+ * checking).
+ *
+ * Note:
+ * \li The first parameter is 1, not 0.
+ */
+#ifdef __GNUC__
+#define ISC_FORMAT_PRINTF(fmt, args) \
+ __attribute__((__format__(__printf__, fmt, args)))
+#else /* ifdef __GNUC__ */
+#define ISC_FORMAT_PRINTF(fmt, args)
+#endif /* ifdef __GNUC__ */
diff --git a/lib/isc/include/isc/fuzz.h b/lib/isc/include/isc/fuzz.h
new file mode 100644
index 0000000..ce322ea
--- /dev/null
+++ b/lib/isc/include/isc/fuzz.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 enum {
+ isc_fuzz_none,
+ isc_fuzz_client,
+ isc_fuzz_tcpclient,
+ isc_fuzz_resolver,
+ isc_fuzz_http,
+ isc_fuzz_rndc
+} isc_fuzztype_t;
diff --git a/lib/isc/include/isc/glob.h b/lib/isc/include/isc/glob.h
new file mode 100644
index 0000000..c9e8d19
--- /dev/null
+++ b/lib/isc/include/isc/glob.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
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+#if HAVE_GLOB_H
+#include <glob.h>
+#else
+#include <stddef.h>
+
+#include <isc/mem.h>
+
+typedef struct {
+ size_t gl_pathc;
+ char **gl_pathv;
+ isc_mem_t *mctx;
+ void *reserved;
+} glob_t;
+
+#endif
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_glob(const char *pattern, glob_t *pglob);
+
+void
+isc_globfree(glob_t *pglob);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h
new file mode 100644
index 0000000..ee9e74f
--- /dev/null
+++ b/lib/isc/include/isc/hash.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include "isc/lang.h"
+#include "isc/types.h"
+
+/***
+ *** Functions
+ ***/
+ISC_LANG_BEGINDECLS
+
+const void *
+isc_hash_get_initializer(void);
+
+void
+isc_hash_set_initializer(const void *initializer);
+
+#define isc_hash_function isc_hash64
+
+uint32_t
+isc_hash32(const void *data, const size_t length, const bool case_sensitive);
+uint64_t
+isc_hash64(const void *data, const size_t length, const bool case_sensitive);
+/*!<
+ * \brief Calculate a hash over data.
+ *
+ * This hash function is useful for hashtables. The hash function is
+ * opaque and not important to the caller. The returned hash values are
+ * non-deterministic and will have different mapping every time a
+ * process using this library is run, but will have uniform
+ * distribution.
+ *
+ * isc_hash_32/64() calculates the hash from start to end over the
+ * input data.
+ *
+ * 'data' is the data to be hashed.
+ *
+ * 'length' is the size of the data to be hashed.
+ *
+ * 'case_sensitive' specifies whether the hash key should be treated as
+ * case_sensitive values. It should typically be false if the hash key
+ * is a DNS name.
+ *
+ * Returns:
+ * \li 32 or 64-bit hash value
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/heap.h b/lib/isc/include/isc/heap.h
new file mode 100644
index 0000000..98ae74c
--- /dev/null
+++ b/lib/isc/include/isc/heap.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
+
+/*! \file isc/heap.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * The comparison function returns true if the first argument has
+ * higher priority than the second argument, and false otherwise.
+ */
+typedef bool (*isc_heapcompare_t)(void *, void *);
+
+/*%
+ * The index function allows the client of the heap to receive a callback
+ * when an item's index number changes. This allows it to maintain
+ * sync with its external state, but still delete itself, since deletions
+ * from the heap require the index be provided.
+ */
+typedef void (*isc_heapindex_t)(void *, unsigned int);
+
+/*%
+ * The heapaction function is used when iterating over the heap.
+ *
+ * NOTE: The heap structure CANNOT BE MODIFIED during the call to
+ * isc_heap_foreach().
+ */
+typedef void (*isc_heapaction_t)(void *, void *);
+
+typedef struct isc_heap isc_heap_t;
+
+void
+isc_heap_create(isc_mem_t *mctx, isc_heapcompare_t compare,
+ isc_heapindex_t index, unsigned int size_increment,
+ isc_heap_t **heapp);
+/*!<
+ * \brief Create a new heap. The heap is implemented using a space-efficient
+ * storage method. When the heap elements are deleted space is not freed
+ * but will be reused when new elements are inserted.
+ *
+ * Heap elements are indexed from 1.
+ *
+ * Requires:
+ *\li "mctx" is valid.
+ *\li "compare" is a function which takes two void * arguments and
+ * returns true if the first argument has a higher priority than
+ * the second, and false otherwise.
+ *\li "index" is a function which takes a void *, and an unsigned int
+ * argument. This function will be called whenever an element's
+ * index value changes, so it may continue to delete itself from the
+ * heap. This option may be NULL if this functionality is unneeded.
+ *\li "size_increment" is a hint about how large the heap should grow
+ * when resizing is needed. If this is 0, a default size will be
+ * used, which is currently 1024, allowing space for an additional 1024
+ * heap elements to be inserted before adding more space.
+ *\li "heapp" is not NULL, and "*heap" is NULL.
+ */
+
+void
+isc_heap_destroy(isc_heap_t **heapp);
+/*!<
+ * \brief Destroys a heap.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ */
+
+void
+isc_heap_insert(isc_heap_t *heap, void *elt);
+/*!<
+ * \brief Inserts a new element into a heap.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ */
+
+void
+isc_heap_delete(isc_heap_t *heap, unsigned int index);
+/*!<
+ * \brief Deletes an element from a heap, by element index.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ *\li "index" is a valid element index, as provided by the "index" callback
+ * provided during heap creation.
+ */
+
+void
+isc_heap_increased(isc_heap_t *heap, unsigned int index);
+/*!<
+ * \brief Indicates to the heap that an element's priority has increased.
+ * This function MUST be called whenever an element has increased in priority.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ *\li "index" is a valid element index, as provided by the "index" callback
+ * provided during heap creation.
+ */
+
+void
+isc_heap_decreased(isc_heap_t *heap, unsigned int index);
+/*!<
+ * \brief Indicates to the heap that an element's priority has decreased.
+ * This function MUST be called whenever an element has decreased in priority.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ *\li "index" is a valid element index, as provided by the "index" callback
+ * provided during heap creation.
+ */
+
+void *
+isc_heap_element(isc_heap_t *heap, unsigned int index);
+/*!<
+ * \brief Returns the element for a specific element index.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ *\li "index" is a valid element index, as provided by the "index" callback
+ * provided during heap creation.
+ *
+ * Returns:
+ *\li A pointer to the element for the element index.
+ */
+
+void
+isc_heap_foreach(isc_heap_t *heap, isc_heapaction_t action, void *uap);
+/*!<
+ * \brief Iterate over the heap, calling an action for each element. The
+ * order of iteration is not sorted.
+ *
+ * Requires:
+ *\li "heapp" is not NULL and "*heap" points to a valid isc_heap_t.
+ *\li "action" is not NULL, and is a function which takes two arguments.
+ * The first is a void *, representing the element, and the second is
+ * "uap" as provided to isc_heap_foreach.
+ *\li "uap" is a caller-provided argument, and may be NULL.
+ *
+ * Note:
+ *\li The heap structure CANNOT be modified during this iteration. The only
+ * safe function to call while iterating the heap is isc_heap_element().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/hex.h b/lib/isc/include/isc/hex.h
new file mode 100644
index 0000000..d5f714e
--- /dev/null
+++ b/lib/isc/include/isc/hex.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/hex.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+isc_hex_totext(isc_region_t *source, int wordlength, const char *wordbreak,
+ isc_buffer_t *target);
+/*!<
+ * \brief Convert data into hex encoded text.
+ *
+ * Notes:
+ *\li The hex encoded text in 'target' will be divided into
+ * words of at most 'wordlength' characters, separated by
+ * the 'wordbreak' string. No parentheses will surround
+ * the text.
+ *
+ * Requires:
+ *\li 'source' is a region containing binary data
+ *\li 'target' is a text buffer containing available space
+ *\li 'wordbreak' points to a null-terminated string of
+ * zero or more whitespace characters
+ *
+ * Ensures:
+ *\li target will contain the hex encoded version of the data
+ * in source. The 'used' pointer in target will be advanced as
+ * necessary.
+ */
+
+isc_result_t
+isc_hex_decodestring(const char *cstr, isc_buffer_t *target);
+/*!<
+ * \brief Decode a null-terminated hex string.
+ *
+ * Requires:
+ *\li 'cstr' is non-null.
+ *\li 'target' is a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring'
+ * fit in 'target'.
+ *\li #ISC_R_BADHEX -- 'cstr' is not a valid hex encoding.
+ *
+ * Other error returns are any possible error code from:
+ * isc_lex_create(),
+ * isc_lex_openbuffer(),
+ * isc_hex_tobuffer().
+ */
+
+isc_result_t
+isc_hex_tobuffer(isc_lex_t *lexer, isc_buffer_t *target, int length);
+/*!<
+ * \brief Convert hex-encoded text from a lexer context into
+ * `target`. If 'length' is non-negative, it is the expected number of
+ * encoded octets to convert.
+ *
+ * If 'length' is -1 then 0 or more encoded octets are expected.
+ * If 'length' is -2 then 1 or more encoded octets are expected.
+ *
+ * Returns:
+ *\li #ISC_R_BADHEX -- invalid hex encoding
+ *\li #ISC_R_UNEXPECTEDEND: the text does not contain the expected
+ * number of encoded octets.
+ *
+ * Requires:
+ *\li 'lexer' is a valid lexer context
+ *\li 'target' is a buffer containing binary data
+ *\li 'length' is -2, -1, or non-negative
+ *
+ * Ensures:
+ *\li target will contain the data represented by the hex encoded
+ * string parsed by the lexer. No more than `length` octets will
+ * be read, if `length` is non-negative. The 'used' pointer in
+ * 'target' will be advanced as necessary.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/hmac.h b/lib/isc/include/isc/hmac.h
new file mode 100644
index 0000000..68b63d4
--- /dev/null
+++ b/lib/isc/include/isc/hmac.h
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 isc/hmac.h
+ * \brief This is the header for for message authentication code.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/md.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+typedef void isc_hmac_t;
+
+/**
+ * isc_hmac:
+ * @type: the digest type
+ * @key: the key
+ * @keylen: the length of the key
+ * @buf: data to hash
+ * @len: length of the data to hash
+ * @digest: the output buffer
+ * @digestlen: in: the length of @digest
+ * out: the length of the data written to @digest
+ *
+ * This function computes the message authentication code using a digest type
+ * @type with key @key which is @keylen bytes long from data in @buf which is
+ * @len bytes long, and places the output into @digest, which must have space
+ * for the hash function output (use ISC_MAX_MD_SIZE if unsure). @digestlen
+ * is used to pass in the length of the digest buffer and returns the length
+ * of digest written to @digest.
+ */
+isc_result_t
+isc_hmac(const isc_md_type_t *type, const void *key, const size_t keylen,
+ const unsigned char *buf, const size_t len, unsigned char *digest,
+ unsigned int *digestlen);
+
+/**
+ * isc_hmac_new:
+ *
+ * This function allocates, initializes and returns HMAC context.
+ */
+isc_hmac_t *
+isc_hmac_new(void);
+
+/**
+ * isc_hmac_free:
+ * @md: HMAC context
+ *
+ * This function cleans up HMAC context and frees up the space allocated to it.
+ */
+void
+isc_hmac_free(isc_hmac_t *hmac);
+
+/**
+ * isc_hmac_init:
+ * @md: HMAC context
+ * @key: HMAC key
+ * @keylen: HMAC key length
+ * @type: digest type
+ *
+ * This function sets up HMAC context to use a hash function of @type and key
+ * @key which is @keylen bytes long.
+ */
+
+isc_result_t
+isc_hmac_init(isc_hmac_t *hmac, const void *key, const size_t keylen,
+ const isc_md_type_t *type);
+
+/**
+ * isc_hmac_reset:
+ * @hmac: HMAC context
+ *
+ * This function resets the HMAC context. This can be used to reuse an already
+ * existing context.
+ */
+isc_result_t
+isc_hmac_reset(isc_hmac_t *hmac);
+
+/**
+ * isc_hmac_update:
+ * @hmac: HMAC context
+ * @buf: data to hash
+ * @len: length of the data to hash
+ *
+ * This function can be called repeatedly with chunks of the message @buf to be
+ * authenticated which is @len bytes long.
+ */
+isc_result_t
+isc_hmac_update(isc_hmac_t *hmac, const unsigned char *buf, const size_t len);
+
+/**
+ * isc_hmac_final:
+ * @hmac: HMAC context
+ * @digest: the output buffer
+ * @digestlen: in: the length of @digest
+ * out: the length of the data written to @digest
+ *
+ * This function retrieves the message authentication code from @hmac and places
+ * it in @digest, which must have space for the hash function output. @digestlen
+ * is used to pass in the length of the digest buffer and returns the length
+ * of digest written to @digest. After calling this function no additional
+ * calls to isc_hmac_update() can be made.
+ */
+isc_result_t
+isc_hmac_final(isc_hmac_t *hmac, unsigned char *digest,
+ unsigned int *digestlen);
+
+/**
+ * isc_hmac_md_type:
+ * @hmac: HMAC context
+ *
+ * This function return the isc_md_type_t previously set for the supplied
+ * HMAC context or NULL if no isc_md_type_t has been set.
+ */
+const isc_md_type_t *
+isc_hmac_get_md_type(isc_hmac_t *hmac);
+
+/**
+ * isc_hmac_get_size:
+ *
+ * This function return the size of the message digest when passed an isc_hmac_t
+ * structure, i.e. the size of the hash.
+ */
+size_t
+isc_hmac_get_size(isc_hmac_t *hmac);
+
+/**
+ * isc_hmac_get_block_size:
+ *
+ * This function return the block size of the message digest when passed an
+ * isc_hmac_t structure.
+ */
+int
+isc_hmac_get_block_size(isc_hmac_t *hmac);
diff --git a/lib/isc/include/isc/ht.h b/lib/isc/include/isc/ht.h
new file mode 100644
index 0000000..163fbef
--- /dev/null
+++ b/lib/isc/include/isc/ht.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 */
+
+#pragma once
+
+#include <inttypes.h>
+#include <string.h>
+
+#include <isc/result.h>
+#include <isc/types.h>
+
+typedef struct isc_ht isc_ht_t;
+typedef struct isc_ht_iter isc_ht_iter_t;
+
+enum { ISC_HT_CASE_SENSITIVE = 0x00, ISC_HT_CASE_INSENSITIVE = 0x01 };
+
+/*%
+ * Initialize hashtable at *htp, using memory context and size of (1<<bits)
+ *
+ * If 'options' contains ISC_HT_CASE_INSENSITIVE, then upper- and lower-case
+ * letters in key values will generate the same hash values; this can be used
+ * when the key for a hash table is a DNS name.
+ *
+ * Requires:
+ *\li 'htp' is not NULL and '*htp' is NULL.
+ *\li 'mctx' is a valid memory context.
+ *\li 'bits' >=1 and 'bits' <=32
+ *
+ */
+void
+isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits,
+ unsigned int options);
+
+/*%
+ * Destroy hashtable, freeing everything
+ *
+ * Requires:
+ * \li '*htp' is valid hashtable
+ */
+void
+isc_ht_destroy(isc_ht_t **htp);
+
+/*%
+ * Add a node to hashtable, pointed by binary key 'key' of size 'keysize';
+ * set its value to 'value'
+ *
+ * Requires:
+ *\li 'ht' is a valid hashtable
+ *\li write-lock
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY -- not enough memory to create pool
+ *\li #ISC_R_EXISTS -- node of the same key already exists
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+isc_result_t
+isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize,
+ void *value);
+
+/*%
+ * Find a node matching 'key'/'keysize' in hashtable 'ht';
+ * if found, set '*valuep' to its value. (If 'valuep' is NULL,
+ * then simply return SUCCESS or NOTFOUND to indicate whether the
+ * key exists in the hashtable.)
+ *
+ * Requires:
+ * \li 'ht' is a valid hashtable
+ * \li read-lock
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS -- success
+ * \li #ISC_R_NOTFOUND -- key not found
+ */
+isc_result_t
+isc_ht_find(const isc_ht_t *ht, const unsigned char *key,
+ const uint32_t keysize, void **valuep);
+
+/*%
+ * Delete node from hashtable
+ *
+ * Requires:
+ *\li ht is a valid hashtable
+ *\li write-lock
+ *
+ * Returns:
+ *\li #ISC_R_NOTFOUND -- key not found
+ *\li #ISC_R_SUCCESS -- all is well
+ */
+isc_result_t
+isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize);
+
+/*%
+ * Create an iterator for the hashtable; point '*itp' to it.
+ *
+ * Requires:
+ *\li 'ht' is a valid hashtable
+ *\li 'itp' is non NULL and '*itp' is NULL.
+ */
+void
+isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp);
+
+/*%
+ * Destroy the iterator '*itp', set it to NULL
+ *
+ * Requires:
+ *\li 'itp' is non NULL and '*itp' is non NULL.
+ */
+void
+isc_ht_iter_destroy(isc_ht_iter_t **itp);
+
+/*%
+ * Set an iterator to the first entry.
+ *
+ * Requires:
+ *\li 'it' is non NULL.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS -- success
+ * \li #ISC_R_NOMORE -- no data in the hashtable
+ */
+isc_result_t
+isc_ht_iter_first(isc_ht_iter_t *it);
+
+/*%
+ * Set an iterator to the next entry.
+ *
+ * Requires:
+ *\li 'it' is non NULL.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS -- success
+ * \li #ISC_R_NOMORE -- end of hashtable reached
+ */
+isc_result_t
+isc_ht_iter_next(isc_ht_iter_t *it);
+
+/*%
+ * Delete current entry and set an iterator to the next entry.
+ *
+ * Requires:
+ *\li 'it' is non NULL.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS -- success
+ * \li #ISC_R_NOMORE -- end of hashtable reached
+ */
+isc_result_t
+isc_ht_iter_delcurrent_next(isc_ht_iter_t *it);
+
+/*%
+ * Set 'value' to the current value under the iterator
+ *
+ * Requires:
+ *\li 'it' is non NULL.
+ *\li 'valuep' is non NULL and '*valuep' is NULL.
+ */
+void
+isc_ht_iter_current(isc_ht_iter_t *it, void **valuep);
+
+/*%
+ * Set 'key' and 'keysize to the current key and keysize for the value
+ * under the iterator
+ *
+ * Requires:
+ *\li 'it' is non NULL.
+ *\li 'key' is non NULL and '*key' is NULL.
+ *\li 'keysize' is non NULL.
+ */
+void
+isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, size_t *keysize);
+
+/*%
+ * Returns the number of items in the hashtable.
+ *
+ * Requires:
+ *\li 'ht' is a valid hashtable
+ */
+size_t
+isc_ht_count(const isc_ht_t *ht);
diff --git a/lib/isc/include/isc/httpd.h b/lib/isc/include/isc/httpd.h
new file mode 100644
index 0000000..1f69a4c
--- /dev/null
+++ b/lib/isc/include/isc/httpd.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/eventclass.h>
+#include <isc/mutex.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/types.h>
+#include <isc/url.h>
+
+#define HTTPD_EVENTCLASS ISC_EVENTCLASS(4300)
+#define HTTPD_SHUTDOWN (HTTPD_EVENTCLASS + 0x0001)
+
+#define ISC_HTTPDMGR_SHUTTINGDOWN 0x00000001
+
+typedef isc_result_t(isc_httpdaction_t)(
+ const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
+ unsigned int *retcode, const char **retmsg, const char **mimetype,
+ isc_buffer_t *body, isc_httpdfree_t **freecb, void **freecb_args);
+
+typedef bool(isc_httpdclientok_t)(const isc_sockaddr_t *, void *);
+
+isc_result_t
+isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr,
+ isc_httpdclientok_t *client_ok,
+ isc_httpdondestroy_t *ondestroy, void *cb_arg,
+ isc_httpdmgr_t **httpdmgrp);
+
+void
+isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdp);
+
+isc_result_t
+isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
+ isc_httpdaction_t *func, void *arg);
+
+void
+isc_httpd_setfinishhook(void (*fn)(void));
+
+bool
+isc_httpdurl_isstatic(const isc_httpdurl_t *url);
+
+const isc_time_t *
+isc_httpdurl_loadtime(const isc_httpdurl_t *url);
+
+const isc_time_t *
+isc_httpd_if_modified_since(const isc_httpd_t *httpd);
diff --git a/lib/isc/include/isc/interfaceiter.h b/lib/isc/include/isc/interfaceiter.h
new file mode 100644
index 0000000..c696091
--- /dev/null
+++ b/lib/isc/include/isc/interfaceiter.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/interfaceiter.h
+ * \brief Iterates over the list of network interfaces.
+ *
+ * Interfaces whose address family is not supported are ignored and never
+ * returned by the iterator. Interfaces whose netmask, interface flags,
+ * or similar cannot be obtained are also ignored, and the failure is logged.
+ *
+ * Standards:
+ * The API for scanning varies greatly among operating systems.
+ * This module attempts to hide the differences.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/netaddr.h>
+#include <isc/types.h>
+
+/*!
+ * \brief Public structure describing a network interface.
+ */
+
+struct isc_interface {
+ char name[32]; /*%< Interface name, null-terminated. */
+ unsigned int af; /*%< Address family. */
+ isc_netaddr_t address; /*%< Local address. */
+ isc_netaddr_t netmask; /*%< Network mask. */
+ isc_netaddr_t dstaddress; /*%< Destination address
+ * (point-to-point
+ * only). */
+ uint32_t flags; /*%< Flags; see INTERFACE flags. */
+};
+
+/*@{*/
+/*! Interface flags. */
+
+#define INTERFACE_F_UP 0x00000001U
+#define INTERFACE_F_POINTTOPOINT 0x00000002U
+#define INTERFACE_F_LOOPBACK 0x00000004U
+/*@}*/
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp);
+/*!<
+ * \brief Create an iterator for traversing the operating system's list
+ * of network interfaces.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ *\li Various network-related errors
+ */
+
+isc_result_t
+isc_interfaceiter_first(isc_interfaceiter_t *iter);
+/*!<
+ * \brief Position the iterator on the first interface.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOMORE There are no interfaces.
+ */
+
+isc_result_t
+isc_interfaceiter_current(isc_interfaceiter_t *iter, isc_interface_t *ifdata);
+/*!<
+ * \brief Get information about the interface the iterator is currently
+ * positioned at and store it at *ifdata.
+ *
+ * Requires:
+ *\li The iterator has been successfully positioned using
+ * isc_interface_iter_first() / isc_interface_iter_next().
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ */
+
+isc_result_t
+isc_interfaceiter_next(isc_interfaceiter_t *iter);
+/*!<
+ * \brief Position the iterator on the next interface.
+ *
+ * Requires:
+ * \li The iterator has been successfully positioned using
+ * isc_interface_iter_first() / isc_interface_iter_next().
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOMORE There are no more interfaces.
+ */
+
+void
+isc_interfaceiter_destroy(isc_interfaceiter_t **iterp);
+/*!<
+ * \brief Destroy the iterator.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/iterated_hash.h b/lib/isc/include/isc/iterated_hash.h
new file mode 100644
index 0000000..b5d6ab6
--- /dev/null
+++ b/lib/isc/include/isc/iterated_hash.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+
+/*
+ * The maximal hash length that can be encoded in a name
+ * using base32hex. floor(255/8)*5
+ */
+#define NSEC3_MAX_HASH_LENGTH 155
+
+/*
+ * The maximum has that can be encoded in a single label using
+ * base32hex. floor(63/8)*5
+ */
+#define NSEC3_MAX_LABEL_HASH 35
+
+ISC_LANG_BEGINDECLS
+
+int
+isc_iterated_hash(unsigned char *out, const unsigned int hashalg,
+ const int iterations, const unsigned char *salt,
+ const int saltlength, const unsigned char *in,
+ const int inlength);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/lang.h b/lib/isc/include/isc/lang.h
new file mode 100644
index 0000000..b7e88a5
--- /dev/null
+++ b/lib/isc/include/isc/lang.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
+
+/*! \file isc/lang.h */
+
+#ifdef __cplusplus
+#define ISC_LANG_BEGINDECLS extern "C" {
+#define ISC_LANG_ENDDECLS }
+#else /* ifdef __cplusplus */
+#define ISC_LANG_BEGINDECLS
+#define ISC_LANG_ENDDECLS
+#endif /* ifdef __cplusplus */
diff --git a/lib/isc/include/isc/lex.h b/lib/isc/include/isc/lex.h
new file mode 100644
index 0000000..14b29cf
--- /dev/null
+++ b/lib/isc/include/isc/lex.h
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/lex.h
+ * \brief The "lex" module provides a lightweight tokenizer. It can operate
+ * on files or buffers, and can handle "include". It is designed for
+ * parsing of DNS master files and the BIND configuration file, but
+ * should be general enough to tokenize other things, e.g. HTTP.
+ *
+ * \li MP:
+ * No synchronization is provided. Clients must ensure exclusive
+ * access.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * TBS
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/region.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Options
+ ***/
+
+/*@{*/
+/*!
+ * Various options for isc_lex_gettoken().
+ */
+
+#define ISC_LEXOPT_EOL 0x0001 /*%< Want end-of-line token. */
+#define ISC_LEXOPT_EOF 0x0002 /*%< Want end-of-file token. */
+#define ISC_LEXOPT_INITIALWS 0x0004 /*%< Want initial whitespace. */
+#define ISC_LEXOPT_NUMBER 0x0008 /*%< Recognize numbers. */
+#define ISC_LEXOPT_QSTRING 0x0010 /*%< Recognize qstrings. */
+/*@}*/
+
+/*@{*/
+/*!
+ * The ISC_LEXOPT_DNSMULTILINE option handles the processing of '(' and ')' in
+ * the DNS master file format. If this option is set, then the
+ * ISC_LEXOPT_INITIALWS and ISC_LEXOPT_EOL options will be ignored when
+ * the paren count is > 0. To use this option, '(' and ')' must be special
+ * characters.
+ */
+#define ISC_LEXOPT_DNSMULTILINE 0x0020 /*%< Handle '(' and ')'. */
+#define ISC_LEXOPT_NOMORE 0x0040 /*%< Want "no more" token. */
+
+#define ISC_LEXOPT_CNUMBER 0x0080 /*%< Recognize octal and hex. */
+#define ISC_LEXOPT_ESCAPE 0x0100 /*%< Recognize escapes. */
+#define ISC_LEXOPT_QSTRINGMULTILINE 0x0200 /*%< Allow multiline "" strings */
+#define ISC_LEXOPT_OCTAL 0x0400 /*%< Expect a octal number. */
+#define ISC_LEXOPT_BTEXT 0x0800 /*%< Bracketed text. */
+#define ISC_LEXOPT_VPAIR 0x1000 /*%< Recognize value pair. */
+#define ISC_LEXOPT_QVPAIR 0x2000 /*%< Recognize quoted value pair. */
+/*@}*/
+/*@{*/
+/*!
+ * Various commenting styles, which may be changed at any time with
+ * isc_lex_setcomments().
+ */
+
+#define ISC_LEXCOMMENT_C 0x01
+#define ISC_LEXCOMMENT_CPLUSPLUS 0x02
+#define ISC_LEXCOMMENT_SHELL 0x04
+#define ISC_LEXCOMMENT_DNSMASTERFILE 0x08
+/*@}*/
+
+/***
+ *** Types
+ ***/
+
+/*! Lex */
+
+typedef char isc_lexspecials_t[256];
+
+/* Tokens */
+
+typedef enum {
+ isc_tokentype_unknown = 0,
+ isc_tokentype_string = 1,
+ isc_tokentype_number = 2,
+ isc_tokentype_qstring = 3,
+ isc_tokentype_eol = 4,
+ isc_tokentype_eof = 5,
+ isc_tokentype_initialws = 6,
+ isc_tokentype_special = 7,
+ isc_tokentype_nomore = 8,
+ isc_tokentype_btext = 9,
+ isc_tokentype_vpair = 10,
+ isc_tokentype_qvpair = 11,
+} isc_tokentype_t;
+
+typedef union {
+ char as_char;
+ unsigned long as_ulong;
+ isc_region_t as_region;
+ isc_textregion_t as_textregion;
+ void *as_pointer;
+} isc_tokenvalue_t;
+
+typedef struct isc_token {
+ isc_tokentype_t type;
+ isc_tokenvalue_t value;
+} isc_token_t;
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+isc_lex_create(isc_mem_t *mctx, size_t max_token, isc_lex_t **lexp);
+/*%<
+ * Create a lexer.
+ *
+ * 'max_token' is a hint of the number of bytes in the largest token.
+ *
+ * Requires:
+ *\li '*lexp' is a valid lexer.
+ *
+ * Ensures:
+ *\li On success, *lexp is attached to the newly created lexer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+isc_lex_destroy(isc_lex_t **lexp);
+/*%<
+ * Destroy the lexer.
+ *
+ * Requires:
+ *\li '*lexp' is a valid lexer.
+ *
+ * Ensures:
+ *\li *lexp == NULL
+ */
+
+unsigned int
+isc_lex_getcomments(isc_lex_t *lex);
+/*%<
+ * Return the current lexer commenting styles.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ * Returns:
+ *\li The commenting styles which are currently allowed.
+ */
+
+void
+isc_lex_setcomments(isc_lex_t *lex, unsigned int comments);
+/*%<
+ * Set allowed lexer commenting styles.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'comments' has meaningful values.
+ */
+
+void
+isc_lex_getspecials(isc_lex_t *lex, isc_lexspecials_t specials);
+/*%<
+ * Put the current list of specials into 'specials'.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ */
+
+void
+isc_lex_setspecials(isc_lex_t *lex, isc_lexspecials_t specials);
+/*!<
+ * The characters in 'specials' are returned as tokens. Along with
+ * whitespace, they delimit strings and numbers.
+ *
+ * Note:
+ *\li Comment processing takes precedence over special character
+ * recognition.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ */
+
+isc_result_t
+isc_lex_openfile(isc_lex_t *lex, const char *filename);
+/*%<
+ * Open 'filename' and make it the current input source for 'lex'.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li filename is a valid C string.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY Out of memory
+ *\li #ISC_R_NOTFOUND File not found
+ *\li #ISC_R_NOPERM No permission to open file
+ *\li #ISC_R_FAILURE Couldn't open file, not sure why
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_lex_openstream(isc_lex_t *lex, FILE *stream);
+/*%<
+ * Make 'stream' the current input source for 'lex'.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'stream' is a valid C stream.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY Out of memory
+ */
+
+isc_result_t
+isc_lex_openbuffer(isc_lex_t *lex, isc_buffer_t *buffer);
+/*%<
+ * Make 'buffer' the current input source for 'lex'.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'buffer' is a valid buffer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY Out of memory
+ */
+
+isc_result_t
+isc_lex_close(isc_lex_t *lex);
+/*%<
+ * Close the most recently opened object (i.e. file or buffer).
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMORE No more input sources
+ */
+
+isc_result_t
+isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp);
+/*%<
+ * Get the next token.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'lex' has an input source.
+ *
+ *\li 'options' contains valid options.
+ *
+ *\li '*tokenp' is a valid pointer.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_UNEXPECTEDEND
+ *\li #ISC_R_NOMEMORY
+ *
+ * These two results are returned only if their corresponding lexer
+ * options are not set.
+ *
+ *\li #ISC_R_EOF End of input source
+ *\li #ISC_R_NOMORE No more input sources
+ */
+
+isc_result_t
+isc_lex_getmastertoken(isc_lex_t *lex, isc_token_t *token,
+ isc_tokentype_t expect, bool eol);
+/*%<
+ * Get the next token from a DNS master file type stream. This is a
+ * convenience function that sets appropriate options and handles quoted
+ * strings and end of line correctly for master files. It also ungets
+ * unexpected tokens. If `eol` is set then expect end-of-line otherwise
+ * eol is a error.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'token' is a valid pointer
+ *
+ * Returns:
+ *
+ * \li any return code from isc_lex_gettoken().
+ */
+
+isc_result_t
+isc_lex_getoctaltoken(isc_lex_t *lex, isc_token_t *token, bool eol);
+/*%<
+ * Get the next token from a DNS master file type stream. This is a
+ * convenience function that sets appropriate options and handles end
+ * of line correctly for master files. It also ungets unexpected tokens.
+ * If `eol` is set then expect end-of-line otherwise eol is a error.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'token' is a valid pointer
+ *
+ * Returns:
+ *
+ * \li any return code from isc_lex_gettoken().
+ */
+
+void
+isc_lex_ungettoken(isc_lex_t *lex, isc_token_t *tokenp);
+/*%<
+ * Unget the current token.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'lex' has an input source.
+ *
+ *\li 'tokenp' points to a valid token.
+ *
+ *\li There is no ungotten token already.
+ */
+
+void
+isc_lex_getlasttokentext(isc_lex_t *lex, isc_token_t *tokenp, isc_region_t *r);
+/*%<
+ * Returns a region containing the text of the last token returned.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ *\li 'lex' has an input source.
+ *
+ *\li 'tokenp' points to a valid token.
+ *
+ *\li A token has been gotten and not ungotten.
+ */
+
+char *
+isc_lex_getsourcename(isc_lex_t *lex);
+/*%<
+ * Return the input source name.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ * Returns:
+ * \li source name or NULL if no current source.
+ *\li result valid while current input source exists.
+ */
+
+unsigned long
+isc_lex_getsourceline(isc_lex_t *lex);
+/*%<
+ * Return the input source line number.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ * Returns:
+ *\li Current line number or 0 if no current source.
+ */
+
+isc_result_t
+isc_lex_setsourcename(isc_lex_t *lex, const char *name);
+/*%<
+ * Assigns a new name to the input source.
+ *
+ * Requires:
+ *
+ * \li 'lex' is a valid lexer.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ * \li #ISC_R_NOTFOUND - there are no sources.
+ */
+
+isc_result_t
+isc_lex_setsourceline(isc_lex_t *lex, unsigned long line);
+/*%<
+ * Assigns a new line number to the input source. This can be used
+ * when parsing a buffer that's been excerpted from the middle a file,
+ * allowing logged messages to display the correct line number,
+ * rather than the line number within the buffer.
+ *
+ * Requires:
+ *
+ * \li 'lex' is a valid lexer.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND - there are no sources.
+ */
+
+bool
+isc_lex_isfile(isc_lex_t *lex);
+/*%<
+ * Return whether the current input source is a file.
+ *
+ * Requires:
+ *\li 'lex' is a valid lexer.
+ *
+ * Returns:
+ * \li #true if the current input is a file,
+ *\li #false otherwise.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/list.h b/lib/isc/include/isc/list.h
new file mode 100644
index 0000000..2cf4437
--- /dev/null
+++ b/lib/isc/include/isc/list.h
@@ -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.
+ */
+
+#pragma once
+
+#include <isc/assertions.h>
+
+#define ISC_LINK_TOMBSTONE(type) ((type *)-1)
+
+#define ISC_LIST_INITIALIZER \
+ { \
+ .head = NULL, .tail = NULL, \
+ }
+#define ISC_LINK_INITIALIZER_TYPE(type) \
+ { \
+ .prev = ISC_LINK_TOMBSTONE(type), \
+ .next = ISC_LINK_TOMBSTONE(type), \
+ }
+#define ISC_LINK_INITIALIZER ISC_LINK_INITIALIZER_TYPE(void)
+
+#ifdef ISC_LIST_CHECKINIT
+#define ISC_LINK_INSIST(x) ISC_INSIST(x)
+#else /* ifdef ISC_LIST_CHECKINIT */
+#define ISC_LINK_INSIST(x)
+#endif /* ifdef ISC_LIST_CHECKINIT */
+
+#define ISC_LIST(type) \
+ struct { \
+ type *head, *tail; \
+ }
+#define ISC_LIST_INIT(list) \
+ do { \
+ (list).head = NULL; \
+ (list).tail = NULL; \
+ } while (0)
+
+#define ISC_LINK(type) \
+ struct { \
+ type *prev, *next; \
+ }
+#define ISC_LINK_INIT_TYPE(elt, link, type) \
+ do { \
+ (elt)->link.prev = ISC_LINK_TOMBSTONE(type); \
+ (elt)->link.next = ISC_LINK_TOMBSTONE(type); \
+ } while (0)
+#define ISC_LINK_INIT(elt, link) ISC_LINK_INIT_TYPE(elt, link, void)
+#define ISC_LINK_LINKED_TYPE(elt, link, type) \
+ ((type *)((elt)->link.prev) != ISC_LINK_TOMBSTONE(type))
+#define ISC_LINK_LINKED(elt, link) ISC_LINK_LINKED_TYPE(elt, link, void)
+
+#define ISC_LIST_HEAD(list) ((list).head)
+#define ISC_LIST_TAIL(list) ((list).tail)
+#define ISC_LIST_EMPTY(list) ((list).head == NULL)
+
+#define __ISC_LIST_PREPENDUNSAFE(list, elt, link) \
+ do { \
+ if ((list).head != NULL) { \
+ (list).head->link.prev = (elt); \
+ } else { \
+ (list).tail = (elt); \
+ } \
+ (elt)->link.prev = NULL; \
+ (elt)->link.next = (list).head; \
+ (list).head = (elt); \
+ } while (0)
+
+#define ISC_LIST_PREPEND(list, elt, link) \
+ do { \
+ ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \
+ __ISC_LIST_PREPENDUNSAFE(list, elt, link); \
+ } while (0)
+
+#define ISC_LIST_INITANDPREPEND(list, elt, link) \
+ __ISC_LIST_PREPENDUNSAFE(list, elt, link)
+
+#define __ISC_LIST_APPENDUNSAFE(list, elt, link) \
+ do { \
+ if ((list).tail != NULL) { \
+ (list).tail->link.next = (elt); \
+ } else { \
+ (list).head = (elt); \
+ } \
+ (elt)->link.prev = (list).tail; \
+ (elt)->link.next = NULL; \
+ (list).tail = (elt); \
+ } while (0)
+
+#define ISC_LIST_APPEND(list, elt, link) \
+ do { \
+ ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \
+ __ISC_LIST_APPENDUNSAFE(list, elt, link); \
+ } while (0)
+
+#define ISC_LIST_INITANDAPPEND(list, elt, link) \
+ __ISC_LIST_APPENDUNSAFE(list, elt, link)
+
+#define __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type) \
+ do { \
+ if ((elt)->link.next != NULL) { \
+ (elt)->link.next->link.prev = (elt)->link.prev; \
+ } else { \
+ ISC_INSIST((list).tail == (elt)); \
+ (list).tail = (elt)->link.prev; \
+ } \
+ if ((elt)->link.prev != NULL) { \
+ (elt)->link.prev->link.next = (elt)->link.next; \
+ } else { \
+ ISC_INSIST((list).head == (elt)); \
+ (list).head = (elt)->link.next; \
+ } \
+ (elt)->link.prev = ISC_LINK_TOMBSTONE(type); \
+ (elt)->link.next = ISC_LINK_TOMBSTONE(type); \
+ ISC_INSIST((list).head != (elt)); \
+ ISC_INSIST((list).tail != (elt)); \
+ } while (0)
+
+#define __ISC_LIST_UNLINKUNSAFE(list, elt, link) \
+ __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, void)
+
+#define ISC_LIST_UNLINK_TYPE(list, elt, link, type) \
+ do { \
+ ISC_LINK_INSIST(ISC_LINK_LINKED(elt, link)); \
+ __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type); \
+ } while (0)
+#define ISC_LIST_UNLINK(list, elt, link) \
+ ISC_LIST_UNLINK_TYPE(list, elt, link, void)
+
+#define ISC_LIST_PREV(elt, link) ((elt)->link.prev)
+#define ISC_LIST_NEXT(elt, link) ((elt)->link.next)
+
+#define __ISC_LIST_INSERTBEFOREUNSAFE(list, before, elt, link) \
+ do { \
+ if ((before)->link.prev == NULL) { \
+ ISC_LIST_PREPEND(list, elt, link); \
+ } else { \
+ (elt)->link.prev = (before)->link.prev; \
+ (before)->link.prev = (elt); \
+ (elt)->link.prev->link.next = (elt); \
+ (elt)->link.next = (before); \
+ } \
+ } while (0)
+
+#define ISC_LIST_INSERTBEFORE(list, before, elt, link) \
+ do { \
+ ISC_LINK_INSIST(ISC_LINK_LINKED(before, link)); \
+ ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \
+ __ISC_LIST_INSERTBEFOREUNSAFE(list, before, elt, link); \
+ } while (0)
+
+#define __ISC_LIST_INSERTAFTERUNSAFE(list, after, elt, link) \
+ do { \
+ if ((after)->link.next == NULL) { \
+ ISC_LIST_APPEND(list, elt, link); \
+ } else { \
+ (elt)->link.next = (after)->link.next; \
+ (after)->link.next = (elt); \
+ (elt)->link.next->link.prev = (elt); \
+ (elt)->link.prev = (after); \
+ } \
+ } while (0)
+
+#define ISC_LIST_INSERTAFTER(list, after, elt, link) \
+ do { \
+ ISC_LINK_INSIST(ISC_LINK_LINKED(after, link)); \
+ ISC_LINK_INSIST(!ISC_LINK_LINKED(elt, link)); \
+ __ISC_LIST_INSERTAFTERUNSAFE(list, after, elt, link); \
+ } while (0)
+
+#define ISC_LIST_APPENDLIST(list1, list2, link) \
+ do { \
+ if (ISC_LIST_EMPTY(list1)) { \
+ (list1) = (list2); \
+ } else if (!ISC_LIST_EMPTY(list2)) { \
+ (list1).tail->link.next = (list2).head; \
+ (list2).head->link.prev = (list1).tail; \
+ (list1).tail = (list2).tail; \
+ } \
+ (list2).head = NULL; \
+ (list2).tail = NULL; \
+ } while (0)
+
+#define ISC_LIST_PREPENDLIST(list1, list2, link) \
+ do { \
+ if (ISC_LIST_EMPTY(list1)) { \
+ (list1) = (list2); \
+ } else if (!ISC_LIST_EMPTY(list2)) { \
+ (list2).tail->link.next = (list1).head; \
+ (list1).head->link.prev = (list2).tail; \
+ (list1).head = (list2).head; \
+ } \
+ (list2).head = NULL; \
+ (list2).tail = NULL; \
+ } while (0)
+
+#define ISC_LIST_ENQUEUE(list, elt, link) ISC_LIST_APPEND(list, elt, link)
+#define __ISC_LIST_ENQUEUEUNSAFE(list, elt, link) \
+ __ISC_LIST_APPENDUNSAFE(list, elt, link)
+#define ISC_LIST_DEQUEUE(list, elt, link) \
+ ISC_LIST_UNLINK_TYPE(list, elt, link, void)
+#define ISC_LIST_DEQUEUE_TYPE(list, elt, link, type) \
+ ISC_LIST_UNLINK_TYPE(list, elt, link, type)
+#define __ISC_LIST_DEQUEUEUNSAFE(list, elt, link) \
+ __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, void)
+#define __ISC_LIST_DEQUEUEUNSAFE_TYPE(list, elt, link, type) \
+ __ISC_LIST_UNLINKUNSAFE_TYPE(list, elt, link, type)
+
+#define ISC_LIST_MOVEUNSAFE(dest, src) \
+ { \
+ (dest).head = (src).head; \
+ (dest).tail = (src).tail; \
+ (src).head = NULL; \
+ (src).tail = NULL; \
+ }
+
+#define ISC_LIST_MOVE(dest, src) \
+ { \
+ INSIST(ISC_LIST_EMPTY(dest)); \
+ ISC_LIST_MOVEUNSAFE(dest, src); \
+ }
diff --git a/lib/isc/include/isc/log.h b/lib/isc/include/isc/log.h
new file mode 100644
index 0000000..54b4022
--- /dev/null
+++ b/lib/isc/include/isc/log.h
@@ -0,0 +1,853 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/log.h */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <syslog.h> /* XXXDCL NT */
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*@{*/
+/*!
+ * \brief Severity levels, patterned after Unix's syslog levels.
+ *
+ */
+#define ISC_LOG_DEBUG(level) (level)
+/*!
+ * #ISC_LOG_DYNAMIC can only be used for defining channels with
+ * isc_log_createchannel(), not to specify a level in isc_log_write().
+ */
+#define ISC_LOG_DYNAMIC 0
+#define ISC_LOG_INFO (-1)
+#define ISC_LOG_NOTICE (-2)
+#define ISC_LOG_WARNING (-3)
+#define ISC_LOG_ERROR (-4)
+#define ISC_LOG_CRITICAL (-5)
+/*@}*/
+
+/*@{*/
+/*!
+ * \brief Destinations.
+ */
+#define ISC_LOG_TONULL 1
+#define ISC_LOG_TOSYSLOG 2
+#define ISC_LOG_TOFILE 3
+#define ISC_LOG_TOFILEDESC 4
+/*@}*/
+
+/*@{*/
+/*%
+ * Channel flags.
+ */
+#define ISC_LOG_PRINTTIME 0x00001
+#define ISC_LOG_PRINTLEVEL 0x00002
+#define ISC_LOG_PRINTCATEGORY 0x00004
+#define ISC_LOG_PRINTMODULE 0x00008
+#define ISC_LOG_PRINTTAG 0x00010 /* tag and ":" */
+#define ISC_LOG_PRINTPREFIX 0x00020 /* tag only, no colon */
+#define ISC_LOG_PRINTALL 0x0003F
+#define ISC_LOG_BUFFERED 0x00040
+#define ISC_LOG_DEBUGONLY 0x01000
+#define ISC_LOG_OPENERR 0x08000 /* internal */
+#define ISC_LOG_ISO8601 0x10000 /* if PRINTTIME, use ISO8601 */
+#define ISC_LOG_UTC 0x20000 /* if PRINTTIME, use UTC */
+/*@}*/
+
+/*@{*/
+/*!
+ * \brief Other options.
+ *
+ * XXXDCL INFINITE doesn't yet work. Arguably it isn't needed, but
+ * since I am intend to make large number of versions work efficiently,
+ * INFINITE is going to be trivial to add to that.
+ */
+#define ISC_LOG_ROLLINFINITE (-1)
+#define ISC_LOG_ROLLNEVER (-2)
+#define ISC_LOG_MAX_VERSIONS 256
+/*@}*/
+
+/*@{*/
+/*!
+ * \brief Type of suffix used on rolled log files.
+ */
+typedef enum {
+ isc_log_rollsuffix_increment,
+ isc_log_rollsuffix_timestamp
+} isc_log_rollsuffix_t;
+/*@}*/
+
+/*!
+ * \brief Used to name the categories used by a library.
+ *
+ * An array of isc_logcategory
+ * structures names each category, and the id value is initialized by calling
+ * isc_log_registercategories.
+ */
+struct isc_logcategory {
+ const char *name;
+ unsigned int id;
+};
+
+/*%
+ * Similar to isc_logcategory, but for all the modules a library defines.
+ */
+struct isc_logmodule {
+ const char *name;
+ unsigned int id;
+};
+
+/*%
+ * The isc_logfile structure is initialized as part of an isc_logdestination
+ * before calling isc_log_createchannel().
+ *
+ * When defining an #ISC_LOG_TOFILE
+ * channel the name, versions and maximum_size should be set before calling
+ * isc_log_createchannel(). To define an #ISC_LOG_TOFILEDESC channel set only
+ * the stream before the call.
+ *
+ * Setting maximum_size to zero implies no maximum.
+ */
+typedef struct isc_logfile {
+ FILE *stream; /*%< Initialized to NULL for
+ * #ISC_LOG_TOFILE. */
+ const char *name; /*%< NULL for #ISC_LOG_TOFILEDESC. */
+ int versions; /* >= 0, #ISC_LOG_ROLLNEVER,
+ * #ISC_LOG_ROLLINFINITE. */
+ isc_log_rollsuffix_t suffix;
+ /*%
+ * stdio's ftell is standardized to return a long, which may well not
+ * be big enough for the largest file supportable by the operating
+ * system (though it is _probably_ big enough for the largest log
+ * anyone would want). st_size returned by fstat should be typedef'd
+ * to a size large enough for the largest possible file on a system.
+ */
+ isc_offset_t maximum_size;
+ bool maximum_reached; /*%< Private. */
+} isc_logfile_t;
+
+/*%
+ * Passed to isc_log_createchannel to define the attributes of either
+ * a stdio or a syslog log.
+ */
+typedef union isc_logdestination {
+ isc_logfile_t file;
+ int facility; /* XXXDCL NT */
+} isc_logdestination_t;
+
+/*@{*/
+/*%
+ * The built-in categories of libisc.
+ *
+ * Each library registering categories should provide library_LOGCATEGORY_name
+ * definitions with indexes into its isc_logcategory structure corresponding to
+ * the order of the names.
+ */
+extern isc_logcategory_t isc_categories[];
+extern isc_log_t *isc_lctx;
+extern isc_logmodule_t isc_modules[];
+/*@}*/
+
+/*@{*/
+/*%
+ * Do not log directly to DEFAULT. Use another category. When in doubt,
+ * use GENERAL.
+ */
+#define ISC_LOGCATEGORY_DEFAULT (&isc_categories[0])
+#define ISC_LOGCATEGORY_GENERAL (&isc_categories[1])
+#define ISC_LOGCATEGORY_SSLKEYLOG (&isc_categories[2])
+/*@}*/
+
+#define ISC_LOGMODULE_SOCKET (&isc_modules[0])
+#define ISC_LOGMODULE_TIME (&isc_modules[1])
+#define ISC_LOGMODULE_INTERFACE (&isc_modules[2])
+#define ISC_LOGMODULE_TIMER (&isc_modules[3])
+#define ISC_LOGMODULE_FILE (&isc_modules[4])
+#define ISC_LOGMODULE_NETMGR (&isc_modules[5])
+#define ISC_LOGMODULE_OTHER (&isc_modules[6])
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp);
+/*%<
+ * Establish a new logging context, with default channels.
+ *
+ * Notes:
+ *\li isc_log_create() calls isc_logconfig_create(), so see its comment
+ * below for more information.
+ *
+ * Requires:
+ *\li mctx is a valid memory context.
+ *\li lctxp is not null and *lctxp is null.
+ *\li lcfgp is null or lcfgp is not null and *lcfgp is null.
+ *
+ * Ensures:
+ *\li *lctxp will point to a valid logging context if all of the necessary
+ * memory was allocated, or NULL otherwise.
+ *\li *lcfgp will point to a valid logging configuration if all of the
+ * necessary memory was allocated, or NULL otherwise.
+ *\li On failure, no additional memory is allocated.
+ */
+
+void
+isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp);
+/*%<
+ * Create the data structure that holds all of the configurable information
+ * about where messages are actually supposed to be sent -- the information
+ * that could changed based on some configuration file, as opposed to the
+ * the category/module specification of isc_log_[v]write[1] that is compiled
+ * into a program, or the debug_level which is dynamic state information.
+ *
+ * Notes:
+ *\li It is necessary to specify the logging context the configuration
+ * will be used with because the number of categories and modules
+ * needs to be known in order to set the configuration. However,
+ * the configuration is not used by the logging context until the
+ * isc_logconfig_use function is called.
+ *
+ *\li The memory context used for operations that allocate memory for
+ * the configuration is that of the logging context, as specified
+ * in the isc_log_create call.
+ *
+ *\li Four default channels are established:
+ *\verbatim
+ * default_syslog
+ * - log to syslog's daemon facility #ISC_LOG_INFO or higher
+ * default_stderr
+ * - log to stderr #ISC_LOG_INFO or higher
+ * default_debug
+ * - log to stderr #ISC_LOG_DEBUG dynamically
+ * null
+ * - log nothing
+ *\endverbatim
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *\li lcftp is not null and *lcfgp is null.
+ *
+ * Ensures:
+ *\li *lcfgp will point to a valid logging context if all of the necessary
+ * memory was allocated, or NULL otherwise.
+ *\li On failure, no additional memory is allocated.
+ */
+
+void
+isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg);
+/*%<
+ * Associate a new configuration with a logging context.
+ *
+ * Notes:
+ *\li This is thread safe. The logging context will lock a mutex
+ * before attempting to swap in the new configuration, and isc_log_doit
+ * (the internal function used by all of isc_log_[v]write[1]) locks
+ * the same lock for the duration of its use of the configuration.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *\li lcfg is a valid logging configuration.
+ *\li lctx is the same configuration given to isc_logconfig_create
+ * when the configuration was created.
+ *
+ * Ensures:
+ *\li Future calls to isc_log_write will use the new configuration.
+ */
+
+void
+isc_log_destroy(isc_log_t **lctxp);
+/*%<
+ * Deallocate the memory associated with a logging context.
+ *
+ * Requires:
+ *\li *lctx is a valid logging context.
+ *
+ * Ensures:
+ *\li All of the memory associated with the logging context is returned
+ * to the free memory pool.
+ *
+ *\li Any open files are closed.
+ *
+ *\li The logging context is marked as invalid.
+ */
+
+void
+isc_logconfig_destroy(isc_logconfig_t **lcfgp);
+/*%<
+ * Destroy a logging configuration.
+ *
+ * Requires:
+ *\li lcfgp is not null and *lcfgp is a valid logging configuration.
+ *\li The logging configuration is not in use by an existing logging context.
+ *
+ * Ensures:
+ *\li All memory allocated for the configuration is freed.
+ *
+ *\li The configuration is marked as invalid.
+ */
+
+void
+isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]);
+/*%<
+ * Identify logging categories a library will use.
+ *
+ * Notes:
+ *\li A category should only be registered once, but no mechanism enforces
+ * this rule.
+ *
+ *\li The end of the categories array is identified by a NULL name.
+ *
+ *\li Because the name is used by #ISC_LOG_PRINTCATEGORY, it should not
+ * be altered or destroyed after isc_log_registercategories().
+ *
+ *\li Because each element of the categories array is used by
+ * isc_log_categorybyname, it should not be altered or destroyed
+ * after registration.
+ *
+ *\li The value of the id integer in each structure is overwritten
+ * by this function, and so id need not be initialized to any particular
+ * value prior to the function call.
+ *
+ *\li A subsequent call to isc_log_registercategories with the same
+ * logging context (but new categories) will cause the last
+ * element of the categories array from the prior call to have
+ * its "name" member changed from NULL to point to the new
+ * categories array, and its "id" member set to UINT_MAX.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *\li categories != NULL.
+ *\li categories[0].name != NULL.
+ *
+ * Ensures:
+ * \li There are references to each category in the logging context,
+ * so they can be used with isc_log_usechannel() and isc_log_write().
+ */
+
+void
+isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]);
+/*%<
+ * Identify logging categories a library will use.
+ *
+ * Notes:
+ *\li A module should only be registered once, but no mechanism enforces
+ * this rule.
+ *
+ *\li The end of the modules array is identified by a NULL name.
+ *
+ *\li Because the name is used by #ISC_LOG_PRINTMODULE, it should not
+ * be altered or destroyed after isc_log_registermodules().
+ *
+ *\li Because each element of the modules array is used by
+ * isc_log_modulebyname, it should not be altered or destroyed
+ * after registration.
+ *
+ *\li The value of the id integer in each structure is overwritten
+ * by this function, and so id need not be initialized to any particular
+ * value prior to the function call.
+ *
+ *\li A subsequent call to isc_log_registermodules with the same
+ * logging context (but new modules) will cause the last
+ * element of the modules array from the prior call to have
+ * its "name" member changed from NULL to point to the new
+ * modules array, and its "id" member set to UINT_MAX.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *\li modules != NULL.
+ *\li modules[0].name != NULL;
+ *
+ * Ensures:
+ *\li Each module has a reference in the logging context, so they can be
+ * used with isc_log_usechannel() and isc_log_write().
+ */
+
+void
+isc_log_createchannel(isc_logconfig_t *lcfg, const char *name,
+ unsigned int type, int level,
+ const isc_logdestination_t *destination,
+ unsigned int flags);
+/*%<
+ * Specify the parameters of a logging channel.
+ *
+ * Notes:
+ *\li The name argument is copied to memory in the logging context, so
+ * it can be altered or destroyed after isc_log_createchannel().
+ *
+ *\li Defining a very large number of channels will have a performance
+ * impact on isc_log_usechannel(), since the names are searched
+ * linearly until a match is made. This same issue does not affect
+ * isc_log_write, however.
+ *
+ *\li Channel names can be redefined; this is primarily useful for programs
+ * that want their own definition of default_syslog, default_debug
+ * and default_stderr.
+ *
+ *\li Any channel that is redefined will not affect logging that was
+ * already directed to its original definition, _except_ for the
+ * default_stderr channel. This case is handled specially so that
+ * the default logging category can be changed by redefining
+ * default_stderr. (XXXDCL Though now that I think of it, the default
+ * logging category can be changed with only one additional function
+ * call by defining a new channel and then calling isc_log_usechannel()
+ * for #ISC_LOGCATEGORY_DEFAULT.)
+ *
+ *\li Specifying #ISC_LOG_PRINTTIME or #ISC_LOG_PRINTTAG for syslog is
+ * allowed, but probably not what you wanted to do.
+ *
+ * #ISC_LOG_DEBUGONLY will mark the channel as usable only when the
+ * debug level of the logging context (see isc_log_setdebuglevel)
+ * is non-zero.
+ *
+ * Requires:
+ *\li lcfg is a valid logging configuration.
+ *
+ *\li name is not NULL.
+ *
+ *\li type is #ISC_LOG_TOSYSLOG, #ISC_LOG_TOFILE, #ISC_LOG_TOFILEDESC or
+ * #ISC_LOG_TONULL.
+ *
+ *\li destination is not NULL unless type is #ISC_LOG_TONULL.
+ *
+ *\li level is >= #ISC_LOG_CRITICAL (the most negative logging level).
+ *
+ *\li flags does not include any bits aside from the ISC_LOG_PRINT* bits,
+ * #ISC_LOG_DEBUGONLY or #ISC_LOG_BUFFERED.
+ *
+ * Ensures:
+ *\li #ISC_R_SUCCESS
+ * A channel with the given name is usable with
+ * isc_log_usechannel().
+ *
+ *\li #ISC_R_NOMEMORY or #ISC_R_UNEXPECTED
+ * No additional memory is being used by the logging context.
+ * Any channel that previously existed with the given name
+ * is not redefined.
+ */
+
+isc_result_t
+isc_log_usechannel(isc_logconfig_t *lcfg, const char *name,
+ const isc_logcategory_t *category,
+ const isc_logmodule_t *module);
+/*%<
+ * Associate a named logging channel with a category and module that
+ * will use it.
+ *
+ * Notes:
+ *\li The name is searched for linearly in the set of known channel names
+ * until a match is found. (Note the performance impact of a very large
+ * number of named channels.) When multiple channels of the same
+ * name are defined, the most recent definition is found.
+ *
+ *\li Specifying a very large number of channels for a category will have
+ * a moderate impact on performance in isc_log_write(), as each
+ * call looks up the category for the start of a linked list, which
+ * it follows all the way to the end to find matching modules. The
+ * test for matching modules is integral, though.
+ *
+ *\li If category is NULL, then the channel is associated with the indicated
+ * module for all known categories (including the "default" category).
+ *
+ *\li If module is NULL, then the channel is associated with every module
+ * that uses that category.
+ *
+ *\li Passing both category and module as NULL would make every log message
+ * use the indicated channel.
+ *
+ * \li Specifying a channel that is #ISC_LOG_TONULL for a category/module pair
+ * has no effect on any other channels associated with that pair,
+ * regardless of ordering. Thus you cannot use it to "mask out" one
+ * category/module pair when you have specified some other channel that
+ * is also used by that category/module pair.
+ *
+ * Requires:
+ *\li lcfg is a valid logging configuration.
+ *
+ *\li category is NULL or has an id that is in the range of known ids.
+ *
+ * module is NULL or has an id that is in the range of known ids.
+ *
+ * Ensures:
+ *\li #ISC_R_SUCCESS
+ * The channel will be used by the indicated category/module
+ * arguments.
+ *
+ *\li #ISC_R_NOMEMORY
+ * If assignment for a specific category has been requested,
+ * the channel has not been associated with the indicated
+ * category/module arguments and no additional memory is
+ * used by the logging context.
+ * If assignment for all categories has been requested
+ * then _some_ may have succeeded (starting with category
+ * "default" and progressing through the order of categories
+ * passed to isc_log_registercategories()) and additional memory
+ * is being used by whatever assignments succeeded.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOMEMORY Resource limit: Out of memory
+ */
+
+/* Attention: next four comments PRECEDE code */
+/*!
+ * \brief
+ * Write a message to the log channels.
+ *
+ * Notes:
+ *\li lctx can be NULL; this is allowed so that programs which use
+ * libraries that use the ISC logging system are not required to
+ * also use it.
+ *
+ *\li The format argument is a printf(3) string, with additional arguments
+ * as necessary.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li The category and module arguments must have ids that are in the
+ * range of known ids, as established by isc_log_registercategories()
+ * and isc_log_registermodules().
+ *
+ *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define
+ * channels, and explicit debugging level must be identified for
+ * isc_log_write() via ISC_LOG_DEBUG(level).
+ *
+ *\li format != NULL.
+ *
+ * Ensures:
+ *\li The log message is written to every channel associated with the
+ * indicated category/module pair.
+ *
+ * Returns:
+ *\li Nothing. Failure to log a message is not construed as a
+ * meaningful error.
+ */
+void
+isc_log_write(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format, ...)
+
+ ISC_FORMAT_PRINTF(5, 6);
+
+/*%
+ * Write a message to the log channels.
+ *
+ * Notes:
+ *\li lctx can be NULL; this is allowed so that programs which use
+ * libraries that use the ISC logging system are not required to
+ * also use it.
+ *
+ *\li The format argument is a printf(3) string, with additional arguments
+ * as necessary.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li The category and module arguments must have ids that are in the
+ * range of known ids, as established by isc_log_registercategories()
+ * and isc_log_registermodules().
+ *
+ *\li level != #ISC_LOG_DYNAMIC. ISC_LOG_DYNAMIC is used only to define
+ * channels, and explicit debugging level must be identified for
+ * isc_log_write() via ISC_LOG_DEBUG(level).
+ *
+ *\li format != NULL.
+ *
+ * Ensures:
+ *\li The log message is written to every channel associated with the
+ * indicated category/module pair.
+ *
+ * Returns:
+ *\li Nothing. Failure to log a message is not construed as a
+ * meaningful error.
+ */
+void
+isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format,
+ va_list args)
+
+ ISC_FORMAT_PRINTF(5, 0);
+
+/*%
+ * Write a message to the log channels, pruning duplicates that occur within
+ * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval).
+ * This function is otherwise identical to isc_log_write().
+ */
+void
+isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format, ...)
+
+ ISC_FORMAT_PRINTF(5, 6);
+
+/*%
+ * Write a message to the log channels, pruning duplicates that occur within
+ * a configurable amount of seconds (see isc_log_[sg]etduplicateinterval).
+ * This function is otherwise identical to isc_log_vwrite().
+ */
+void
+isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format,
+ va_list args)
+
+ ISC_FORMAT_PRINTF(5, 0);
+
+void
+isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level);
+/*%<
+ * Set the debugging level used for logging.
+ *
+ * Notes:
+ *\li Setting the debugging level to 0 disables debugging log messages.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ * Ensures:
+ *\li The debugging level is set to the requested value.
+ */
+
+unsigned int
+isc_log_getdebuglevel(isc_log_t *lctx);
+/*%<
+ * Get the current debugging level.
+ *
+ * Notes:
+ *\li This is provided so that a program can have a notion of
+ * "increment debugging level" or "decrement debugging level"
+ * without needing to keep track of what the current level is.
+ *
+ *\li A return value of 0 indicates that debugging messages are disabled.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ * Ensures:
+ *\li The current logging debugging level is returned.
+ */
+
+bool
+isc_log_wouldlog(isc_log_t *lctx, int level);
+/*%<
+ * Determine whether logging something to 'lctx' at 'level' would
+ * actually cause something to be logged somewhere.
+ *
+ * If #false is returned, it is guaranteed that nothing would
+ * be logged, allowing the caller to omit unnecessary
+ * isc_log_write() calls and possible message preformatting.
+ */
+
+void
+isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval);
+/*%<
+ * Set the interval over which duplicate log messages will be ignored
+ * by isc_log_[v]write1(), in seconds.
+ *
+ * Notes:
+ *\li Increasing the duplicate interval from X to Y will not necessarily
+ * filter out duplicates of messages logged in Y - X seconds since the
+ * increase. (Example: Message1 is logged at midnight. Message2
+ * is logged at 00:01:00, when the interval is only 30 seconds, causing
+ * Message1 to be expired from the log message history. Then the interval
+ * is increased to 3000 (five minutes) and at 00:04:00 Message1 is logged
+ * again. It will appear the second time even though less than five
+ * passed since the first occurrence.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ */
+
+unsigned int
+isc_log_getduplicateinterval(isc_logconfig_t *lcfg);
+/*%<
+ * Get the current duplicate filtering interval.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ * Returns:
+ *\li The current duplicate filtering interval.
+ */
+
+void
+isc_log_settag(isc_logconfig_t *lcfg, const char *tag);
+/*%<
+ * Set the program name or other identifier for #ISC_LOG_PRINTTAG.
+ *
+ * Requires:
+ *\li lcfg is a valid logging configuration.
+ *
+ * Notes:
+ *\li If this function has not set the tag to a non-NULL, non-empty value,
+ * then the #ISC_LOG_PRINTTAG channel flag will not print anything.
+ * Unlike some implementations of syslog on Unix systems, you *must* set
+ * the tag in order to get it logged. It is not implicitly derived from
+ * the program name (which is pretty impossible to infer portably).
+ *
+ *\li Setting the tag to NULL or the empty string will also cause the
+ * #ISC_LOG_PRINTTAG channel flag to not print anything. If tag equals the
+ * empty string, calls to isc_log_gettag will return NULL.
+ *
+ * XXXDCL when creating a new isc_logconfig_t, it might be nice if the tag
+ * of the currently active isc_logconfig_t was inherited. this does not
+ * currently happen.
+ */
+
+char *
+isc_log_gettag(isc_logconfig_t *lcfg);
+/*%<
+ * Get the current identifier printed with #ISC_LOG_PRINTTAG.
+ *
+ * Requires:
+ *\li lcfg is a valid logging configuration.
+ *
+ * Notes:
+ *\li Since isc_log_settag() will not associate a zero-length string
+ * with the logging configuration, attempts to do so will cause
+ * this function to return NULL. However, a determined programmer
+ * will observe that (currently) a tag of length greater than zero
+ * could be set, and then modified to be zero length.
+ *
+ * Returns:
+ *\li A pointer to the current identifier, or NULL if none has been set.
+ */
+
+void
+isc_log_opensyslog(const char *tag, int options, int facility);
+/*%<
+ * Initialize syslog logging.
+ *
+ * Notes:
+ *\li XXXDCL NT
+ * This is currently equivalent to openlog(), but is not going to remain
+ * that way. In the meantime, the arguments are all identical to
+ * those used by openlog(3), as follows:
+ *
+ * \code
+ * tag: The string to use in the position of the program
+ * name in syslog messages. Most (all?) syslogs
+ * will use basename(argv[0]) if tag is NULL.
+ *
+ * options: LOG_CONS, LOG_PID, LOG_NDELAY ... whatever your
+ * syslog supports.
+ *
+ * facility: The default syslog facility. This is irrelevant
+ * since isc_log_write will ALWAYS use the channel's
+ * declared facility.
+ * \endcode
+ *
+ *\li Zero effort has been made (yet) to accommodate systems with openlog()
+ * that only takes two arguments, or to identify valid syslog
+ * facilities or options for any given architecture.
+ *
+ *\li It is necessary to call isc_log_opensyslog() to initialize
+ * syslogging on machines which do not support network connections to
+ * syslogd because they require a Unix domain socket to be used. Since
+ * this is a chore to determine at run-time, it is suggested that it
+ * always be called by programs using the ISC logging system.
+ *
+ * Requires:
+ *\li Nothing.
+ *
+ * Ensures:
+ *\li openlog() is called to initialize the syslog system.
+ */
+
+void
+isc_log_closefilelogs(isc_log_t *lctx);
+/*%<
+ * Close all open files used by #ISC_LOG_TOFILE channels.
+ *
+ * Notes:
+ *\li This function is provided for programs that want to use their own
+ * log rolling mechanism rather than the one provided internally.
+ * For example, a program that wanted to keep daily logs would define
+ * a channel which used #ISC_LOG_ROLLNEVER, then once a day would
+ * rename the log file and call isc_log_closefilelogs().
+ *
+ *\li #ISC_LOG_TOFILEDESC channels are unaffected.
+ *
+ * Requires:
+ *\li lctx is a valid context.
+ *
+ * Ensures:
+ *\li The open files are closed and will be reopened when they are
+ * next needed.
+ */
+
+isc_logcategory_t *
+isc_log_categorybyname(isc_log_t *lctx, const char *name);
+/*%<
+ * Find a category by its name.
+ *
+ * Notes:
+ *\li The string name of a category is not required to be unique.
+ *
+ * Requires:
+ *\li lctx is a valid context.
+ *\li name is not NULL.
+ *
+ * Returns:
+ *\li A pointer to the _first_ isc_logcategory_t structure used by "name".
+ *
+ *\li NULL if no category exists by that name.
+ */
+
+isc_logmodule_t *
+isc_log_modulebyname(isc_log_t *lctx, const char *name);
+/*%<
+ * Find a module by its name.
+ *
+ * Notes:
+ *\li The string name of a module is not required to be unique.
+ *
+ * Requires:
+ *\li lctx is a valid context.
+ *\li name is not NULL.
+ *
+ * Returns:
+ *\li A pointer to the _first_ isc_logmodule_t structure used by "name".
+ *
+ *\li NULL if no module exists by that name.
+ */
+
+void
+isc_log_setcontext(isc_log_t *lctx);
+/*%<
+ * Sets the context used by the libisc for logging.
+ *
+ * Requires:
+ *\li lctx be a valid context.
+ */
+
+isc_result_t
+isc_logfile_roll(isc_logfile_t *file);
+/*%<
+ * Roll a logfile.
+ *
+ * Requires:
+ *\li file is not NULL.
+ */
+
+void
+isc_log_setforcelog(bool v);
+/*%<
+ * Turn forced logging on/off for the current thread. This can be used to
+ * temporarily increase the debug level to maximum for the duration of
+ * a single task event.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/magic.h b/lib/isc/include/isc/magic.h
new file mode 100644
index 0000000..5e2f184
--- /dev/null
+++ b/lib/isc/include/isc/magic.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 isc/magic.h */
+
+typedef struct {
+ unsigned int magic;
+} isc__magic_t;
+
+/*%
+ * To use this macro the magic number MUST be the first thing in the
+ * structure, and MUST be of type "unsigned int".
+ * The intent of this is to allow magic numbers to be checked even though
+ * the object is otherwise opaque.
+ */
+#define ISC_MAGIC_VALID(a, b) \
+ ((a) != NULL && ((const isc__magic_t *)(a))->magic == (b))
+
+#define ISC_MAGIC(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
diff --git a/lib/isc/include/isc/managers.h b/lib/isc/include/isc/managers.h
new file mode 100644
index 0000000..774ed30
--- /dev/null
+++ b/lib/isc/include/isc/managers.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
+
+#include <isc/netmgr.h>
+#include <isc/result.h>
+#include <isc/task.h>
+#include <isc/timer.h>
+
+typedef struct isc_managers isc_managers_t;
+
+isc_result_t
+isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum,
+ isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
+ isc_timermgr_t **timermgrp);
+
+void
+isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
+ isc_timermgr_t **timermgrp);
diff --git a/lib/isc/include/isc/md.h b/lib/isc/include/isc/md.h
new file mode 100644
index 0000000..f52424b
--- /dev/null
+++ b/lib/isc/include/isc/md.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 isc/md.h
+ * \brief This is the header file for message digest algorithms.
+ */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+typedef void isc_md_t;
+
+/**
+ * isc_md_type_t:
+ * @ISC_MD_MD5: MD5
+ * @ISC_MD_SHA1: SHA-1
+ * @ISC_MD_SHA224: SHA-224
+ * @ISC_MD_SHA256: SHA-256
+ * @ISC_MD_SHA384: SHA-384
+ * @ISC_MD_SHA512: SHA-512
+ *
+ * Enumeration of supported message digest algorithms.
+ */
+typedef void isc_md_type_t;
+
+#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()
+
+const isc_md_type_t *
+isc__md_md5(void);
+const isc_md_type_t *
+isc__md_sha1(void);
+const isc_md_type_t *
+isc__md_sha224(void);
+const isc_md_type_t *
+isc__md_sha256(void);
+const isc_md_type_t *
+isc__md_sha384(void);
+const isc_md_type_t *
+isc__md_sha512(void);
+
+#define ISC_MD5_DIGESTLENGTH isc_md_type_get_size(ISC_MD_MD5)
+#define ISC_MD5_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_MD5)
+#define ISC_SHA1_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA1)
+#define ISC_SHA1_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA1)
+#define ISC_SHA224_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA224)
+#define ISC_SHA224_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA224)
+#define ISC_SHA256_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA256)
+#define ISC_SHA256_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA256)
+#define ISC_SHA384_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA384)
+#define ISC_SHA384_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA384)
+#define ISC_SHA512_DIGESTLENGTH isc_md_type_get_size(ISC_MD_SHA512)
+#define ISC_SHA512_BLOCK_LENGTH isc_md_type_get_block_size(ISC_MD_SHA512)
+
+#define ISC_MAX_MD_SIZE 64U /* EVP_MAX_MD_SIZE */
+#define ISC_MAX_BLOCK_SIZE 128U /* ISC_SHA512_BLOCK_LENGTH */
+
+/**
+ * isc_md:
+ * @type: the digest type
+ * @buf: the data to hash
+ * @len: the length of the data to hash
+ * @digest: the output buffer
+ * @digestlen: the length of the data written to @digest
+ *
+ * This function hashes @len bytes of data at @buf and places the result in
+ * @digest. If the @digestlen parameter is not NULL then the number of bytes of
+ * data written (i.e. the length of the digest) will be written to the integer
+ * at @digestlen, at most ISC_MAX_MD_SIZE bytes will be written.
+ */
+isc_result_t
+isc_md(const isc_md_type_t *type, const unsigned char *buf, const size_t len,
+ unsigned char *digest, unsigned int *digestlen);
+
+/**
+ * isc_md_new:
+ *
+ * This function allocates, initializes and returns a digest context.
+ */
+isc_md_t *
+isc_md_new(void);
+
+/**
+ * isc_md_free:
+ * @md: message digest context
+ *
+ * This function cleans up digest context ctx and frees up the space allocated
+ * to it.
+ */
+void
+isc_md_free(isc_md_t *);
+
+/**
+ * isc_md_init:
+ * @md: message digest context
+ * @type: digest type
+ *
+ * This function sets up digest context @md to use a digest @type. @md must be
+ * initialized before calling this function.
+ */
+isc_result_t
+isc_md_init(isc_md_t *, const isc_md_type_t *md_type);
+
+/**
+ * isc_md_reset:
+ * @md: message digest context
+ *
+ * This function resets the digest context ctx. This can be used to reuse an
+ * already existing context.
+ */
+isc_result_t
+isc_md_reset(isc_md_t *md);
+
+/**
+ * isc_md_update:
+ * @md: message digest context
+ * @buf: data to hash
+ * @len: length of the data to hash
+ *
+ * This function hashes @len bytes of data at @buf into the digest context @md.
+ * This function can be called several times on the same @md to hash additional
+ * data.
+ */
+isc_result_t
+isc_md_update(isc_md_t *md, const unsigned char *buf, const size_t len);
+
+/**
+ * isc_md_final:
+ * @md: message digest context
+ * @digest: the output buffer
+ * @digestlen: the length of the data written to @digest
+ *
+ * This function retrieves the digest value from @md and places it in @digest.
+ * If the @digestlen parameter is not NULL then the number of bytes of data
+ * written (i.e. the length of the digest) will be written to the integer at
+ * @digestlen, at most ISC_MAX_MD_SIZE bytes will be written. After calling
+ * this function no additional calls to isc_md_update() can be made.
+ */
+isc_result_t
+isc_md_final(isc_md_t *md, unsigned char *digest, unsigned int *digestlen);
+
+/**
+ * isc_md_get_type:
+ * @md: message digest contezt
+ *
+ * This function return the isc_md_type_t previously set for the supplied
+ * message digest context or NULL if no isc_md_type_t has been set.
+ */
+const isc_md_type_t *
+isc_md_get_md_type(isc_md_t *md);
+
+/**
+ * isc_md_size:
+ *
+ * This function return the size of the message digest when passed an isc_md_t
+ * structure, i.e. the size of the hash.
+ */
+size_t
+isc_md_get_size(isc_md_t *md);
+
+/**
+ * isc_md_block_size:
+ *
+ * This function return the block size of the message digest when passed an
+ * isc_md_t structure.
+ */
+size_t
+isc_md_get_block_size(isc_md_t *md);
+
+/**
+ * isc_md_size:
+ *
+ * This function return the size of the message digest when passed an
+ * isc_md_type_t , i.e. the size of the hash.
+ */
+size_t
+isc_md_type_get_size(const isc_md_type_t *md_type);
+
+/**
+ * isc_md_block_size:
+ *
+ * This function return the block size of the message digest when passed an
+ * isc_md_type_t.
+ */
+size_t
+isc_md_type_get_block_size(const isc_md_type_t *md_type);
diff --git a/lib/isc/include/isc/mem.h b/lib/isc/include/isc/mem.h
new file mode 100644
index 0000000..bd59f73
--- /dev/null
+++ b/lib/isc/include/isc/mem.h
@@ -0,0 +1,572 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/mem.h */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/attributes.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define ISC_MEM_LOWATER 0
+#define ISC_MEM_HIWATER 1
+typedef void (*isc_mem_water_t)(void *, int);
+
+/*%
+ * Define ISC_MEM_TRACKLINES=1 to turn on detailed tracing of memory
+ * allocation and freeing by file and line number.
+ */
+#ifndef ISC_MEM_TRACKLINES
+#define ISC_MEM_TRACKLINES 0
+#endif /* ifndef ISC_MEM_TRACKLINES */
+
+extern unsigned int isc_mem_debugging;
+extern unsigned int isc_mem_defaultflags;
+
+/*@{*/
+#define ISC_MEM_DEBUGTRACE 0x00000001U
+#define ISC_MEM_DEBUGRECORD 0x00000002U
+#define ISC_MEM_DEBUGUSAGE 0x00000004U
+#define ISC_MEM_DEBUGALL 0x0000001FU
+/*!<
+ * The variable isc_mem_debugging holds a set of flags for
+ * turning certain memory debugging options on or off at
+ * runtime. It is initialized to the value ISC_MEM_DEGBUGGING,
+ * which is 0 by default but may be overridden at compile time.
+ * The following flags can be specified:
+ *
+ * \li #ISC_MEM_DEBUGTRACE
+ * Log each allocation and free to isc_lctx.
+ *
+ * \li #ISC_MEM_DEBUGRECORD
+ * Remember each allocation, and match them up on free.
+ * Crash if a free doesn't match an allocation.
+ *
+ * \li #ISC_MEM_DEBUGUSAGE
+ * If a hi_water mark is set, print the maximum inuse memory
+ * every time it is raised once it exceeds the hi_water mark.
+ */
+/*@}*/
+
+#if ISC_MEM_TRACKLINES
+#define _ISC_MEM_FILELINE , __FILE__, __LINE__
+#define _ISC_MEM_FLARG , const char *, unsigned int
+#else /* if ISC_MEM_TRACKLINES */
+#define _ISC_MEM_FILELINE
+#define _ISC_MEM_FLARG
+#endif /* if ISC_MEM_TRACKLINES */
+
+/*
+ * Flags for isc_mem_create() calls.
+ */
+#define ISC_MEMFLAG_RESERVED1 0x00000001 /* reserved, obsoleted, don't use */
+#define ISC_MEMFLAG_RESERVED2 0x00000002 /* reserved, obsoleted, don't use */
+#define ISC_MEMFLAG_FILL \
+ 0x00000004 /* fill with pattern after alloc and frees */
+
+/*%
+ * Define ISC_MEM_DEFAULTFILL=1 to turn filling the memory with pattern
+ * after alloc and free.
+ */
+#if ISC_MEM_DEFAULTFILL
+#define ISC_MEMFLAG_DEFAULT ISC_MEMFLAG_FILL
+#else /* if !ISC_MEM_USE_INTERNAL_MALLOC */
+#define ISC_MEMFLAG_DEFAULT 0
+#endif /* if !ISC_MEM_USE_INTERNAL_MALLOC */
+
+/*%
+ * isc_mem_putanddetach() is a convenience function for use where you
+ * have a structure with an attached memory context.
+ *
+ * Given:
+ *
+ * \code
+ * struct {
+ * ...
+ * isc_mem_t *mctx;
+ * ...
+ * } *ptr;
+ *
+ * isc_mem_t *mctx;
+ *
+ * isc_mem_putanddetach(&ptr->mctx, ptr, sizeof(*ptr));
+ * \endcode
+ *
+ * is the equivalent of:
+ *
+ * \code
+ * mctx = NULL;
+ * isc_mem_attach(ptr->mctx, &mctx);
+ * isc_mem_detach(&ptr->mctx);
+ * isc_mem_put(mctx, ptr, sizeof(*ptr));
+ * isc_mem_detach(&mctx);
+ * \endcode
+ */
+
+/*%
+ * These functions are actually implemented in isc__mem_<function>
+ * (two underscores). The single-underscore macros are used to pass
+ * __FILE__ and __LINE__, and in the case of the put functions, to
+ * set the pointer being freed to NULL in the calling function.
+ *
+ * Many of these functions have a further isc___mem_<function>
+ * (three underscores) implementation, which is called indirectly
+ * via the isc_memmethods structure in the mctx so that dynamically
+ * loaded modules can use them even if named is statically linked.
+ */
+
+#define ISCMEMFUNC(sfx) isc__mem_##sfx
+#define ISCMEMPOOLFUNC(sfx) isc__mempool_##sfx
+
+#define isc_mem_get(c, s) ISCMEMFUNC(get)((c), (s), 0 _ISC_MEM_FILELINE)
+#define isc_mem_get_aligned(c, s, a) \
+ ISCMEMFUNC(get)((c), (s), (a)_ISC_MEM_FILELINE)
+#define isc_mem_reget(c, p, o, n) \
+ ISCMEMFUNC(reget)((c), (p), (o), (n), 0 _ISC_MEM_FILELINE)
+#define isc_mem_reget_aligned(c, p, o, n, a) \
+ ISCMEMFUNC(reget)((c), (p), (o), (n), (a)_ISC_MEM_FILELINE)
+#define isc_mem_allocate(c, s) ISCMEMFUNC(allocate)((c), (s)_ISC_MEM_FILELINE)
+#define isc_mem_reallocate(c, p, s) \
+ ISCMEMFUNC(reallocate)((c), (p), (s)_ISC_MEM_FILELINE)
+#define isc_mem_strdup(c, p) ISCMEMFUNC(strdup)((c), (p)_ISC_MEM_FILELINE)
+#define isc_mem_strndup(c, p, l) \
+ ISCMEMFUNC(strndup)((c), (p), (l)_ISC_MEM_FILELINE)
+#define isc_mempool_get(c) ISCMEMPOOLFUNC(get)((c)_ISC_MEM_FILELINE)
+
+#define isc_mem_put(c, p, s) \
+ do { \
+ ISCMEMFUNC(put)((c), (p), (s), 0 _ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mem_put_aligned(c, p, s, a) \
+ do { \
+ ISCMEMFUNC(put) \
+ ((c), (p), (s), (a)_ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mem_putanddetach(c, p, s) \
+ do { \
+ ISCMEMFUNC(putanddetach)((c), (p), (s), 0 _ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mem_putanddetach_aligned(c, p, s, a) \
+ do { \
+ ISCMEMFUNC(putanddetach) \
+ ((c), (p), (s), (a)_ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mem_free(c, p) \
+ do { \
+ ISCMEMFUNC(free)((c), (p)_ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+#define isc_mempool_put(c, p) \
+ do { \
+ ISCMEMPOOLFUNC(put)((c), (p)_ISC_MEM_FILELINE); \
+ (p) = NULL; \
+ } while (0)
+
+/*@{*/
+#define isc_mem_create(cp) ISCMEMFUNC(create)((cp)_ISC_MEM_FILELINE)
+void ISCMEMFUNC(create)(isc_mem_t **_ISC_MEM_FLARG);
+
+/*!<
+ * \brief Create a memory context.
+ *
+ * Requires:
+ * mctxp != NULL && *mctxp == NULL */
+/*@}*/
+
+#define isc_mem_create_arena(cp) isc__mem_create_arena((cp)_ISC_MEM_FILELINE)
+void
+isc__mem_create_arena(isc_mem_t **_ISC_MEM_FLARG);
+/*!<
+ * \brief Create a memory context that routs all its operations to a
+ * dedicated jemalloc arena (when available). When jemalloc is not
+ * available, the function is, effectively, an alias to
+ * isc_mem_create().
+ *
+ * Requires:
+ * mctxp != NULL && *mctxp == NULL */
+/*@}*/
+
+isc_result_t
+isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms);
+
+isc_result_t
+isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms);
+/*!<
+ * \brief These two functions set the given parameters on the
+ * jemalloc arena associated with the memory context (if there is
+ * one). When jemalloc is not available, these are no-op.
+ *
+ * NOTE: The "muzzy_decay_ms" and "dirty_decay_ms" are the most common
+ * parameters to adjust when the defaults do not work well (per the
+ * official jemalloc tuning guide:
+ * https://github.com/jemalloc/jemalloc/blob/dev/TUNING.md).
+ *
+ * Requires:
+ * mctx - a valid memory context.
+ */
+/*@}*/
+
+void
+isc_mem_attach(isc_mem_t *, isc_mem_t **);
+
+/*@{*/
+void
+isc_mem_attach(isc_mem_t *, isc_mem_t **);
+#define isc_mem_detach(cp) ISCMEMFUNC(detach)((cp)_ISC_MEM_FILELINE)
+void ISCMEMFUNC(detach)(isc_mem_t **_ISC_MEM_FLARG);
+/*!<
+ * \brief Attach to / detach from a memory context.
+ *
+ * This is intended for applications that use multiple memory contexts
+ * in such a way that it is not obvious when the last allocations from
+ * a given context has been freed and destroying the context is safe.
+ *
+ * Most applications do not need to call these functions as they can
+ * simply create a single memory context at the beginning of main()
+ * and destroy it at the end of main(), thereby guaranteeing that it
+ * is not destroyed while there are outstanding allocations.
+ */
+/*@}*/
+
+#define isc_mem_destroy(cp) ISCMEMFUNC(destroy)((cp)_ISC_MEM_FILELINE)
+void ISCMEMFUNC(destroy)(isc_mem_t **_ISC_MEM_FLARG);
+/*%<
+ * Destroy a memory context.
+ */
+
+void
+isc_mem_stats(isc_mem_t *mctx, FILE *out);
+/*%<
+ * Print memory usage statistics for 'mctx' on the stream 'out'.
+ */
+
+void
+isc_mem_setdestroycheck(isc_mem_t *mctx, bool on);
+/*%<
+ * If 'on' is true, 'mctx' will check for memory leaks when
+ * destroyed and abort the program if any are present.
+ */
+
+size_t
+isc_mem_inuse(isc_mem_t *mctx);
+/*%<
+ * Get an estimate of the amount of memory in use in 'mctx', in bytes.
+ * This includes quantization overhead, but does not include memory
+ * allocated from the system but not yet used.
+ */
+
+size_t
+isc_mem_maxinuse(isc_mem_t *mctx);
+/*%<
+ * Get an estimate of the largest amount of memory that has been in
+ * use in 'mctx' at any time.
+ */
+
+size_t
+isc_mem_total(isc_mem_t *mctx);
+/*%<
+ * Get the total amount of memory in 'mctx', in bytes, including memory
+ * not yet used.
+ */
+
+size_t
+isc_mem_malloced(isc_mem_t *ctx);
+/*%<
+ * Get an estimate of the amount of memory allocated in 'mctx', in bytes.
+ */
+
+size_t
+isc_mem_maxmalloced(isc_mem_t *ctx);
+/*%<
+ * Get an estimate of the largest amount of memory that has been
+ * allocated in 'mctx' at any time.
+ */
+
+bool
+isc_mem_isovermem(isc_mem_t *mctx);
+/*%<
+ * Return true iff the memory context is in "over memory" state, i.e.,
+ * a hiwater mark has been set and the used amount of memory has exceeds
+ * the mark.
+ */
+
+void
+isc_mem_clearwater(isc_mem_t *mctx);
+void
+isc_mem_setwater(isc_mem_t *mctx, isc_mem_water_t water, void *water_arg,
+ size_t hiwater, size_t lowater);
+/*%<
+ * Set high and low water marks for this memory context.
+ *
+ * When the memory usage of 'mctx' exceeds 'hiwater',
+ * '(water)(water_arg, #ISC_MEM_HIWATER)' will be called. 'water' needs
+ * to call isc_mem_waterack() with #ISC_MEM_HIWATER to acknowledge the
+ * state change. 'water' may be called multiple times.
+ *
+ * When the usage drops below 'lowater', 'water' will again be called,
+ * this time with #ISC_MEM_LOWATER. 'water' need to calls
+ * isc_mem_waterack() with #ISC_MEM_LOWATER to acknowledge the change.
+ *
+ * static void
+ * water(void *arg, int mark) {
+ * struct foo *foo = arg;
+ *
+ * LOCK(&foo->marklock);
+ * if (foo->mark != mark) {
+ * foo->mark = mark;
+ * ....
+ * isc_mem_waterack(foo->mctx, mark);
+ * }
+ * UNLOCK(&foo->marklock);
+ * }
+ *
+ * if 'water' is set to NULL, the 'hiwater' and 'lowater' must set to 0, and
+ * high- and low-water processing are disabled for this memory context. There's
+ * a convenient function isc_mem_clearwater().
+ *
+ * Requires:
+ *
+ *\li If 'water' is NULL, 'hiwater' and 'lowater' must be set to 0.
+ *\li If 'water' and 'water_arg' have previously been set, they are
+ unchanged.
+ *\li 'hiwater' >= 'lowater'
+ */
+
+void
+isc_mem_waterack(isc_mem_t *ctx, int mark);
+/*%<
+ * Called to acknowledge changes in signaled by calls to 'water'.
+ */
+
+void
+isc_mem_checkdestroyed(FILE *file);
+/*%<
+ * Check that all memory contexts have been destroyed.
+ * Prints out those that have not been.
+ * Fatally fails if there are still active contexts.
+ */
+
+unsigned int
+isc_mem_references(isc_mem_t *ctx);
+/*%<
+ * Return the current reference count.
+ */
+
+void
+isc_mem_setname(isc_mem_t *ctx, const char *name);
+/*%<
+ * Name 'ctx'.
+ *
+ * Notes:
+ *
+ *\li Only the first 15 characters of 'name' will be copied.
+ *
+ * Requires:
+ *
+ *\li 'ctx' is a valid ctx.
+ */
+
+const char *
+isc_mem_getname(isc_mem_t *ctx);
+/*%<
+ * Get the name of 'ctx', as previously set using isc_mem_setname().
+ *
+ * Requires:
+ *\li 'ctx' is a valid ctx.
+ *
+ * Returns:
+ *\li A non-NULL pointer to a null-terminated string.
+ * If the ctx has not been named, the string is
+ * empty.
+ */
+
+#ifdef HAVE_LIBXML2
+int
+isc_mem_renderxml(void *writer0);
+/*%<
+ * Render all contexts' statistics and status in XML for writer.
+ */
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+isc_result_t
+isc_mem_renderjson(void *memobj0);
+/*%<
+ * Render all contexts' statistics and status in JSON.
+ */
+#endif /* HAVE_JSON_C */
+
+/*
+ * Memory pools
+ */
+
+#define isc_mempool_create(c, s, mp) \
+ isc__mempool_create((c), (s), (mp)_ISC_MEM_FILELINE)
+void
+isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size,
+ isc_mempool_t **mpctxp _ISC_MEM_FLARG);
+/*%<
+ * Create a memory pool.
+ *
+ * Requires:
+ *\li mctx is a valid memory context.
+ *\li size > 0
+ *\li mpctxp != NULL and *mpctxp == NULL
+ *
+ * Defaults:
+ *\li freemax = 1
+ *\li fillcount = 1
+ *
+ * Returns:
+ *\li #ISC_R_NOMEMORY -- not enough memory to create pool
+ *\li #ISC_R_SUCCESS -- all is well.
+ */
+
+#define isc_mempool_destroy(mp) isc__mempool_destroy((mp)_ISC_MEM_FILELINE)
+void
+isc__mempool_destroy(isc_mempool_t **restrict mpctxp _ISC_MEM_FLARG);
+/*%<
+ * Destroy a memory pool.
+ *
+ * Requires:
+ *\li mpctxp != NULL && *mpctxp is a valid pool.
+ *\li The pool has no un"put" allocations outstanding
+ */
+
+void
+isc_mempool_setname(isc_mempool_t *restrict mpctx, const char *name);
+/*%<
+ * Associate a name with a memory pool. At most 15 characters may be
+ *used.
+ *
+ * Requires:
+ *\li mpctx is a valid pool.
+ *\li name != NULL;
+ */
+
+/*
+ * The following functions get/set various parameters. Note that due to
+ * the unlocked nature of pools these are potentially random values
+ *unless the imposed externally provided locking protocols are followed.
+ *
+ * Also note that the quota limits will not always take immediate
+ * effect.
+ *
+ * All functions require (in addition to other requirements):
+ * mpctx is a valid memory pool
+ */
+
+unsigned int
+isc_mempool_getfreemax(isc_mempool_t *restrict mpctx);
+/*%<
+ * Returns the maximum allowed size of the free list.
+ */
+
+void
+isc_mempool_setfreemax(isc_mempool_t *restrict mpctx, const unsigned int limit);
+/*%<
+ * Sets the maximum allowed size of the free list.
+ */
+
+unsigned int
+isc_mempool_getfreecount(isc_mempool_t *restrict mpctx);
+/*%<
+ * Returns current size of the free list.
+ */
+
+unsigned int
+isc_mempool_getallocated(isc_mempool_t *restrict mpctx);
+/*%<
+ * Returns the number of items allocated from this pool.
+ */
+
+unsigned int
+isc_mempool_getfillcount(isc_mempool_t *restrict mpctx);
+/*%<
+ * Returns the number of items allocated as a block from the parent
+ * memory context when the free list is empty.
+ */
+
+void
+isc_mempool_setfillcount(isc_mempool_t *restrict mpctx,
+ const unsigned int limit);
+/*%<
+ * Sets the fillcount.
+ *
+ * Additional requirements:
+ *\li limit > 0
+ */
+
+#if defined(UNIT_TESTING) && defined(malloc)
+/*
+ * cmocka.h redefined malloc as a macro, we #undef it
+ * to avoid replacing ISC_ATTR_MALLOC with garbage.
+ */
+#pragma push_macro("malloc")
+#undef malloc
+#define POP_MALLOC_MACRO 1
+#endif
+
+/*
+ * Pseudo-private functions for use via macros. Do not call directly.
+ */
+void ISCMEMFUNC(putanddetach)(isc_mem_t **, void *, size_t,
+ size_t _ISC_MEM_FLARG);
+void ISCMEMFUNC(put)(isc_mem_t *, void *, size_t, size_t _ISC_MEM_FLARG);
+void ISCMEMFUNC(free)(isc_mem_t *, void *_ISC_MEM_FLARG);
+
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2)
+void *ISCMEMFUNC(get)(isc_mem_t *, size_t, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_DEALLOCATOR_IDX(ISCMEMFUNC(put), 2)
+void *ISCMEMFUNC(reget)(isc_mem_t *, void *, size_t, size_t,
+ size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2)
+void *ISCMEMFUNC(allocate)(isc_mem_t *, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2)
+void *ISCMEMFUNC(reallocate)(isc_mem_t *, void *, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_RETURNS_NONNULL
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2)
+char *ISCMEMFUNC(strdup)(isc_mem_t *, const char *_ISC_MEM_FLARG);
+
+ISC_ATTR_RETURNS_NONNULL
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMFUNC(free), 2)
+char *ISCMEMFUNC(strndup)(isc_mem_t *, const char *, size_t _ISC_MEM_FLARG);
+
+ISC_ATTR_MALLOC_DEALLOCATOR_IDX(ISCMEMPOOLFUNC(put), 2)
+void *ISCMEMPOOLFUNC(get)(isc_mempool_t *_ISC_MEM_FLARG);
+
+void ISCMEMPOOLFUNC(put)(isc_mempool_t *, void *_ISC_MEM_FLARG);
+
+#ifdef POP_MALLOC_MACRO
+/*
+ * Restore cmocka.h macro for malloc.
+ */
+#pragma pop_macro("malloc")
+#endif
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/meminfo.h b/lib/isc/include/isc/meminfo.h
new file mode 100644
index 0000000..fc39e26
--- /dev/null
+++ b/lib/isc/include/isc/meminfo.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
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+uint64_t
+isc_meminfo_totalphys(void);
+/*%<
+ * Return total available physical memory in bytes, or 0 if this cannot
+ * be determined
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/mutex.h b/lib/isc/include/isc/mutex.h
new file mode 100644
index 0000000..b794216
--- /dev/null
+++ b/lib/isc/include/isc/mutex.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 */
+
+#include <pthread.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/result.h> /* for ISC_R_ codes */
+
+ISC_LANG_BEGINDECLS
+
+typedef pthread_mutex_t isc_mutex_t;
+
+int
+isc__mutex_init(isc_mutex_t *mp);
+
+#define isc_mutex_init(mp) \
+ do { \
+ int _err = isc__mutex_init((mp)); \
+ if (_err != 0) { \
+ FATAL_SYSERROR(_err, "pthread_mutex_init()"); \
+ } \
+ } while (0)
+
+#define isc_mutex_lock(mp) \
+ ((pthread_mutex_lock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+
+#define isc_mutex_unlock(mp) \
+ ((pthread_mutex_unlock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
+
+#define isc_mutex_trylock(mp) \
+ ((pthread_mutex_trylock((mp)) == 0) ? ISC_R_SUCCESS : ISC_R_LOCKBUSY)
+
+#define isc_mutex_destroy(mp) RUNTIME_CHECK(pthread_mutex_destroy((mp)) == 0)
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/mutexblock.h b/lib/isc/include/isc/mutexblock.h
new file mode 100644
index 0000000..f5f2e32
--- /dev/null
+++ b/lib/isc/include/isc/mutexblock.h
@@ -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.
+ */
+
+#pragma once
+
+/*! \file isc/mutexblock.h */
+
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_mutexblock_init(isc_mutex_t *block, unsigned int count);
+/*%<
+ * Initialize a block of locks. If an error occurs all initialized locks
+ * will be destroyed, if possible.
+ *
+ * Requires:
+ *
+ *\li block != NULL
+ *
+ *\li count > 0
+ *
+ */
+
+void
+isc_mutexblock_destroy(isc_mutex_t *block, unsigned int count);
+/*%<
+ * Destroy a block of locks.
+ *
+ * Requires:
+ *
+ *\li block != NULL
+ *
+ *\li count > 0
+ *
+ *\li Each lock in the block be initialized via isc_mutex_init() or
+ * the whole block was initialized via isc_mutex_initblock().
+ *
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/net.h b/lib/isc/include/isc/net.h
new file mode 100644
index 0000000..1a3ce63
--- /dev/null
+++ b/lib/isc/include/isc/net.h
@@ -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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * Basic Networking Types
+ *
+ * This module is responsible for defining the following basic networking
+ * types:
+ *
+ *\li struct in_addr
+ *\li struct in6_addr
+ *\li struct in6_pktinfo
+ *\li struct sockaddr
+ *\li struct sockaddr_in
+ *\li struct sockaddr_in6
+ *\li struct sockaddr_storage
+ *\li in_port_t
+ *
+ * It ensures that the AF_ and PF_ macros are defined.
+ *
+ * It declares ntoh[sl]() and hton[sl]().
+ *
+ * It declares inet_ntop(), and inet_pton().
+ *
+ * It ensures that #INADDR_LOOPBACK, #INADDR_ANY, #IN6ADDR_ANY_INIT,
+ * IN6ADDR_V4MAPPED_INIT, in6addr_any, and in6addr_loopback are available.
+ *
+ * It ensures that IN_MULTICAST() is available to check for multicast
+ * addresses.
+ *
+ * MP:
+ *\li No impact.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li N/A.
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li BSD Socket API
+ *\li RFC2553
+ */
+
+/***
+ *** Imports.
+ ***/
+#include <inttypes.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <arpa/inet.h> /* Contractual promise. */
+#include <net/if.h>
+#include <netinet/in.h> /* Contractual promise. */
+#include <sys/socket.h> /* Contractual promise. */
+#include <sys/types.h>
+
+#ifndef IN6ADDR_LOOPBACK_INIT
+#ifdef s6_addr
+/*% IPv6 address loopback init */
+#define IN6ADDR_LOOPBACK_INIT \
+ { \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \
+ } \
+ } \
+ }
+#else /* ifdef s6_addr */
+#define IN6ADDR_LOOPBACK_INIT \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 \
+ } \
+ }
+#endif /* ifdef s6_addr */
+#endif /* ifndef IN6ADDR_LOOPBACK_INIT */
+
+#ifndef IN6ADDR_V4MAPPED_INIT
+#ifdef s6_addr
+/*% IPv6 v4mapped prefix init */
+#define IN6ADDR_V4MAPPED_INIT \
+ { \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, \
+ 0, 0, 0 \
+ } \
+ } \
+ }
+#else /* ifdef s6_addr */
+#define IN6ADDR_V4MAPPED_INIT \
+ { \
+ { \
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 0, 0, 0 \
+ } \
+ }
+#endif /* ifdef s6_addr */
+#endif /* ifndef IN6ADDR_V4MAPPED_INIT */
+
+#ifndef IN6_IS_ADDR_V4MAPPED
+/*% Is IPv6 address V4 mapped? */
+#define IN6_IS_ADDR_V4MAPPED(x) \
+ (memcmp((x)->s6_addr, in6addr_any.s6_addr, 10) == 0 && \
+ (x)->s6_addr[10] == 0xff && (x)->s6_addr[11] == 0xff)
+#endif /* ifndef IN6_IS_ADDR_V4MAPPED */
+
+#ifndef IN6_IS_ADDR_V4COMPAT
+/*% Is IPv6 address V4 compatible? */
+#define IN6_IS_ADDR_V4COMPAT(x) \
+ (memcmp((x)->s6_addr, in6addr_any.s6_addr, 12) == 0 && \
+ ((x)->s6_addr[12] != 0 || (x)->s6_addr[13] != 0 || \
+ (x)->s6_addr[14] != 0 || \
+ ((x)->s6_addr[15] != 0 && (x)->s6_addr[15] != 1)))
+#endif /* ifndef IN6_IS_ADDR_V4COMPAT */
+
+#ifndef IN6_IS_ADDR_MULTICAST
+/*% Is IPv6 address multicast? */
+#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xff)
+#endif /* ifndef IN6_IS_ADDR_MULTICAST */
+
+#ifndef IN6_IS_ADDR_LINKLOCAL
+/*% Is IPv6 address linklocal? */
+#define IN6_IS_ADDR_LINKLOCAL(a) \
+ (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0x80))
+#endif /* ifndef IN6_IS_ADDR_LINKLOCAL */
+
+#ifndef IN6_IS_ADDR_SITELOCAL
+/*% is IPv6 address sitelocal? */
+#define IN6_IS_ADDR_SITELOCAL(a) \
+ (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0xc0))
+#endif /* ifndef IN6_IS_ADDR_SITELOCAL */
+
+#ifndef IN6_IS_ADDR_LOOPBACK
+/*% is IPv6 address loopback? */
+#define IN6_IS_ADDR_LOOPBACK(x) \
+ (memcmp((x)->s6_addr, in6addr_loopback.s6_addr, 16) == 0)
+#endif /* ifndef IN6_IS_ADDR_LOOPBACK */
+
+#ifndef AF_INET6
+/*% IPv6 */
+#define AF_INET6 99
+#endif /* ifndef AF_INET6 */
+
+#ifndef PF_INET6
+/*% IPv6 */
+#define PF_INET6 AF_INET6
+#endif /* ifndef PF_INET6 */
+
+#ifndef INADDR_ANY
+/*% inaddr any */
+#define INADDR_ANY 0x00000000UL
+#endif /* ifndef INADDR_ANY */
+
+#ifndef INADDR_LOOPBACK
+/*% inaddr loopback */
+#define INADDR_LOOPBACK 0x7f000001UL
+#endif /* ifndef INADDR_LOOPBACK */
+
+#ifndef MSG_TRUNC
+/*%
+ * If this system does not have MSG_TRUNC (as returned from recvmsg())
+ * ISC_PLATFORM_RECVOVERFLOW will be defined. This will enable the MSG_TRUNC
+ * faking code in socket.c.
+ */
+#define ISC_PLATFORM_RECVOVERFLOW
+#endif /* ifndef MSG_TRUNC */
+
+/*% IP address. */
+#define ISC__IPADDR(x) ((uint32_t)htonl((uint32_t)(x)))
+
+/*% Is IP address multicast? */
+#define ISC_IPADDR_ISMULTICAST(i) \
+ (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xe0000000))
+
+#define ISC_IPADDR_ISEXPERIMENTAL(i) \
+ (((uint32_t)(i)&ISC__IPADDR(0xf0000000)) == ISC__IPADDR(0xf0000000))
+
+/***
+ *** Functions.
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_net_probeipv4(void);
+/*%<
+ * Check if the system's kernel supports IPv4.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS IPv4 is supported.
+ *\li #ISC_R_NOTFOUND IPv4 is not supported.
+ *\li #ISC_R_DISABLED IPv4 is disabled.
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_net_probeipv6(void);
+/*%<
+ * Check if the system's kernel supports IPv6.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS IPv6 is supported.
+ *\li #ISC_R_NOTFOUND IPv6 is not supported.
+ *\li #ISC_R_DISABLED IPv6 is disabled.
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_net_probe_ipv6only(void);
+/*%<
+ * Check if the system's kernel supports the IPV6_V6ONLY socket option.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS the option is supported for both TCP and UDP.
+ *\li #ISC_R_NOTFOUND IPv6 itself or the option is not supported.
+ *\li #ISC_R_UNEXPECTED
+ */
+
+isc_result_t
+isc_net_probe_ipv6pktinfo(void);
+/*
+ * Check if the system's kernel supports the IPV6_(RECV)PKTINFO socket option
+ * for UDP sockets.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS the option is supported.
+ * \li #ISC_R_NOTFOUND IPv6 itself or the option is not supported.
+ * \li #ISC_R_UNEXPECTED
+ */
+
+void
+isc_net_disableipv4(void);
+
+void
+isc_net_disableipv6(void);
+
+void
+isc_net_enableipv4(void);
+
+void
+isc_net_enableipv6(void);
+
+isc_result_t
+isc_net_probeunix(void);
+/*
+ * Returns whether UNIX domain sockets are supported.
+ */
+
+isc_result_t
+isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high);
+/*%<
+ * Returns system's default range of ephemeral UDP ports, if defined.
+ * If the range is not available or unknown, ISC_NET_PORTRANGELOW and
+ * ISC_NET_PORTRANGEHIGH will be returned.
+ *
+ * Requires:
+ *
+ *\li 'low' and 'high' must be non NULL.
+ *
+ * Returns:
+ *
+ *\li *low and *high will be the ports specifying the low and high ends of
+ * the range.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/netaddr.h b/lib/isc/include/isc/netaddr.h
new file mode 100644
index 0000000..112051f
--- /dev/null
+++ b/lib/isc/include/isc/netaddr.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/netaddr.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+
+#include <sys/types.h>
+#include <sys/un.h>
+
+ISC_LANG_BEGINDECLS
+
+/*
+ * Any updates to this structure should also be applied in
+ * contrib/modules/dlz/dlz_minmal.h.
+ */
+struct isc_netaddr {
+ unsigned int family;
+ union {
+ struct in_addr in;
+ struct in6_addr in6;
+ char un[sizeof(((struct sockaddr_un *)0)->sun_path)];
+ } type;
+ uint32_t zone;
+};
+
+struct isc_netprefix {
+ isc_netaddr_t addr;
+ unsigned int prefixlen;
+};
+
+bool
+isc_netaddr_equal(const isc_netaddr_t *a, const isc_netaddr_t *b);
+
+/*%<
+ * Compare network addresses 'a' and 'b'. Return #true if
+ * they are equal, #false if not.
+ */
+
+bool
+isc_netaddr_eqprefix(const isc_netaddr_t *a, const isc_netaddr_t *b,
+ unsigned int prefixlen);
+/*%<
+ * Compare the 'prefixlen' most significant bits of the network
+ * addresses 'a' and 'b'. If 'b''s scope is zero then 'a''s scope is
+ * ignored. Return #true if they are equal, #false if not.
+ */
+
+isc_result_t
+isc_netaddr_masktoprefixlen(const isc_netaddr_t *s, unsigned int *lenp);
+/*%<
+ * Convert a netmask in 's' into a prefix length in '*lenp'.
+ * The mask should consist of zero or more '1' bits in the
+ * most significant part of the address, followed by '0' bits.
+ * If this is not the case, #ISC_R_MASKNONCONTIG is returned.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_MASKNONCONTIG
+ */
+
+isc_result_t
+isc_netaddr_totext(const isc_netaddr_t *netaddr, isc_buffer_t *target);
+/*%<
+ * Append a text representation of 'sockaddr' to the buffer 'target'.
+ * The text is NOT null terminated. Handles IPv4 and IPv6 addresses.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOSPACE The text or the null termination did not fit.
+ *\li #ISC_R_FAILURE Unspecified failure
+ */
+
+void
+isc_netaddr_format(const isc_netaddr_t *na, char *array, unsigned int size);
+/*%<
+ * Format a human-readable representation of the network address '*na'
+ * into the character array 'array', which is of size 'size'.
+ * The resulting string is guaranteed to be null-terminated.
+ */
+
+#define ISC_NETADDR_FORMATSIZE \
+ sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX%SSSSSSSSSS")
+/*%<
+ * Minimum size of array to pass to isc_netaddr_format().
+ */
+
+void
+isc_netaddr_fromsockaddr(isc_netaddr_t *netaddr, const isc_sockaddr_t *source);
+
+void
+isc_netaddr_fromin(isc_netaddr_t *netaddr, const struct in_addr *ina);
+
+void
+isc_netaddr_fromin6(isc_netaddr_t *netaddr, const struct in6_addr *ina6);
+
+isc_result_t
+isc_netaddr_frompath(isc_netaddr_t *netaddr, const char *path);
+
+void
+isc_netaddr_setzone(isc_netaddr_t *netaddr, uint32_t zone);
+
+uint32_t
+isc_netaddr_getzone(const isc_netaddr_t *netaddr);
+
+void
+isc_netaddr_any(isc_netaddr_t *netaddr);
+/*%<
+ * Return the IPv4 wildcard address.
+ */
+
+void
+isc_netaddr_any6(isc_netaddr_t *netaddr);
+/*%<
+ * Return the IPv6 wildcard address.
+ */
+
+void
+isc_netaddr_unspec(isc_netaddr_t *netaddr);
+/*%<
+ * Initialize as AF_UNSPEC address.
+ */
+
+bool
+isc_netaddr_ismulticast(const isc_netaddr_t *na);
+/*%<
+ * Returns true if the address is a multicast address.
+ */
+
+bool
+isc_netaddr_isexperimental(const isc_netaddr_t *na);
+/*%<
+ * Returns true if the address is a experimental (CLASS E) address.
+ */
+
+bool
+isc_netaddr_islinklocal(const isc_netaddr_t *na);
+/*%<
+ * Returns #true if the address is a link local address.
+ */
+
+bool
+isc_netaddr_issitelocal(const isc_netaddr_t *na);
+/*%<
+ * Returns #true if the address is a site local address.
+ */
+
+bool
+isc_netaddr_isnetzero(const isc_netaddr_t *na);
+/*%<
+ * Returns #true if the address is in net zero.
+ */
+
+void
+isc_netaddr_fromv4mapped(isc_netaddr_t *t, const isc_netaddr_t *s);
+/*%<
+ * Convert an IPv6 v4mapped address into an IPv4 address.
+ */
+
+isc_result_t
+isc_netaddr_prefixok(const isc_netaddr_t *na, unsigned int prefixlen);
+/*
+ * Test whether the netaddr 'na' and 'prefixlen' are consistent.
+ * e.g. prefixlen within range.
+ * na does not have bits set which are not covered by the prefixlen.
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_RANGE prefixlen out of range
+ * ISC_R_NOTIMPLEMENTED unsupported family
+ * ISC_R_FAILURE extra bits.
+ */
+
+bool
+isc_netaddr_isloopback(const isc_netaddr_t *na);
+/*
+ * Test whether the netaddr 'na' is a loopback IPv4 or IPv6 address (in
+ * 127.0.0.0/8 or ::1).
+ */
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/netdb.h b/lib/isc/include/isc/netdb.h
new file mode 100644
index 0000000..93799d7
--- /dev/null
+++ b/lib/isc/include/isc/netdb.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
+ * \brief
+ * Portable netdb.h support.
+ *
+ * This module is responsible for defining the get<x>by<y> APIs.
+ *
+ * MP:
+ *\li No impact.
+ *
+ * Reliability:
+ *\li No anticipated impact.
+ *
+ * Resources:
+ *\li N/A.
+ *
+ * Security:
+ *\li No anticipated impact.
+ *
+ * Standards:
+ *\li BSD API
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <netdb.h>
+
+#include <isc/net.h>
diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h
new file mode 100644
index 0000000..eff33f6
--- /dev/null
+++ b/lib/isc/include/isc/netmgr.h
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <unistd.h>
+
+#include <isc/mem.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/tls.h>
+#include <isc/types.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#if defined(SO_REUSEPORT_LB) || (defined(SO_REUSEPORT) && defined(__linux__))
+#define HAVE_SO_REUSEPORT_LB 1
+#endif
+
+/*
+ * Replacement for isc_sockettype_t provided by socket.h.
+ */
+typedef enum {
+ isc_socktype_tcp = 1,
+ isc_socktype_udp = 2,
+ isc_socktype_unix = 3,
+ isc_socktype_raw = 4
+} isc_socktype_t;
+
+typedef void (*isc_nm_recv_cb_t)(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *cbarg);
+/*%<
+ * Callback function to be used when receiving a packet.
+ *
+ * 'handle' the handle that can be used to send back the answer.
+ * 'eresult' the result of the event.
+ * 'region' contains the received data, if any. It will be freed
+ * after return by caller.
+ * 'cbarg' the callback argument passed to isc_nm_listenudp(),
+ * isc_nm_listentcpdns(), or isc_nm_read().
+ */
+typedef isc_result_t (*isc_nm_accept_cb_t)(isc_nmhandle_t *handle,
+ isc_result_t result, void *cbarg);
+/*%<
+ * Callback function to be used when accepting a connection. (This differs
+ * from isc_nm_cb_t below in that it returns a result code.)
+ *
+ * 'handle' the handle that can be used to send back the answer.
+ * 'eresult' the result of the event.
+ * 'cbarg' the callback argument passed to isc_nm_listentcp() or
+ * isc_nm_listentcpdns().
+ */
+
+typedef void (*isc_nm_cb_t)(isc_nmhandle_t *handle, isc_result_t result,
+ void *cbarg);
+/*%<
+ * Callback function for other network completion events (send, connect).
+ *
+ * 'handle' the handle on which the event took place.
+ * 'eresult' the result of the event.
+ * 'cbarg' the callback argument passed to isc_nm_send(),
+ * isc_nm_tcp_connect(), or isc_nm_listentcp()
+ */
+
+typedef void (*isc_nm_opaquecb_t)(void *arg);
+/*%<
+ * Opaque callback function, used for isc_nmhandle 'reset' and 'free'
+ * callbacks.
+ */
+
+typedef void (*isc_nm_workcb_t)(void *arg);
+typedef void (*isc_nm_after_workcb_t)(void *arg, isc_result_t result);
+/*%<
+ * Callback functions for libuv threadpool work (see uv_work_t)
+ */
+
+void
+isc_nm_attach(isc_nm_t *mgr, isc_nm_t **dst);
+void
+isc_nm_detach(isc_nm_t **mgr0);
+/*%<
+ * Attach/detach a network manager. When all references have been
+ * released, the network manager is shut down, freeing all resources.
+ * Destroy is working the same way as detach, but it actively waits
+ * for all other references to be gone.
+ */
+
+/* Return thread ID of current thread, or ISC_NETMGR_TID_UNKNOWN */
+int
+isc_nm_tid(void);
+
+void
+isc_nmsocket_close(isc_nmsocket_t **sockp);
+/*%<
+ * isc_nmsocket_close() detaches a listening socket that was
+ * created by isc_nm_listenudp(), isc_nm_listentcp(), or
+ * isc_nm_listentcpdns(). Once there are no remaining child
+ * sockets with active handles, the socket will be closed.
+ */
+
+void
+isc_nmsocket_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx);
+/*%<
+ * Asynchronously replace the TLS context within the listener socket object.
+ * The function is intended to be used during reconfiguration.
+ *
+ * Requires:
+ * \li 'listener' is a pointer to a valid network manager listener socket
+ object with TLS support;
+ * \li 'tlsctx' is a valid pointer to a TLS context object.
+ */
+
+void
+isc_nmsocket_set_max_streams(isc_nmsocket_t *listener,
+ const uint32_t max_streams);
+/*%<
+ * Set the maximum allowed number of concurrent streams for accepted
+ * client connections. The implementation might be asynchronous
+ * depending on the listener socket type.
+ *
+ * The call is a no-op for any listener socket type that does not
+ * support concept of multiple sessions per a client
+ * connection. Currently, it works only for HTTP/2 listeners.
+ *
+ * Setting 'max_streams' to '0' instructs the listener that there is
+ * no limit for concurrent streams.
+ *
+ * Requires:
+ * \li 'listener' is a pointer to a valid network manager listener socket.
+ */
+
+#ifdef NETMGR_TRACE
+#define isc_nmhandle_attach(handle, dest) \
+ isc__nmhandle_attach(handle, dest, __FILE__, __LINE__, __func__)
+#define isc_nmhandle_detach(handlep) \
+ isc__nmhandle_detach(handlep, __FILE__, __LINE__, __func__)
+#define FLARG , const char *file, unsigned int line, const char *func
+#else
+#define isc_nmhandle_attach(handle, dest) isc__nmhandle_attach(handle, dest)
+#define isc_nmhandle_detach(handlep) isc__nmhandle_detach(handlep)
+#define FLARG
+#endif
+
+void
+isc__nmhandle_attach(isc_nmhandle_t *handle, isc_nmhandle_t **dest FLARG);
+void
+isc__nmhandle_detach(isc_nmhandle_t **handlep FLARG);
+/*%<
+ * Increment/decrement the reference counter in a netmgr handle,
+ * but (unlike the attach/detach functions) do not change the pointer
+ * value. If reference counters drop to zero, the handle can be
+ * marked inactive, possibly triggering deletion of its associated
+ * socket.
+ *
+ * (This will be used to prevent a client from being cleaned up when
+ * it's passed to an isc_task event handler. The libuv code would not
+ * otherwise know that the handle was in use and might free it, along
+ * with the client.)
+ */
+#undef FLARG
+
+void *
+isc_nmhandle_getdata(isc_nmhandle_t *handle);
+
+void *
+isc_nmhandle_getextra(isc_nmhandle_t *handle);
+
+bool
+isc_nmhandle_is_stream(isc_nmhandle_t *handle);
+
+void
+isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg,
+ isc_nm_opaquecb_t doreset, isc_nm_opaquecb_t dofree);
+/*%<
+ * isc_nmhandle_t has a void* opaque field (for example, ns_client_t).
+ * We reuse handle and `opaque` can also be reused between calls.
+ * This function sets this field and two callbacks:
+ * - doreset resets the `opaque` to initial state
+ * - dofree frees everything associated with `opaque`
+ */
+
+void
+isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+void
+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle);
+/*%<
+ * Set/clear the read/recv timeout for the socket connected to 'handle'
+ * to 'timeout' (in milliseconds), and reset the timer.
+ *
+ * When this is called on a 'wrapper' socket handle (for example,
+ * a TCPDNS socket wrapping a TCP connection), the timer is set for
+ * both socket layers.
+ */
+bool
+isc_nmhandle_timer_running(isc_nmhandle_t *handle);
+/*%<
+ * Return true if the timer for the socket connected to 'handle'
+ * is running.
+ */
+
+void
+isc_nmhandle_keepalive(isc_nmhandle_t *handle, bool value);
+/*%<
+ * Enable/disable keepalive on this connection by setting it to 'value'.
+ *
+ * When keepalive is active, we switch to using the keepalive timeout
+ * to determine when to close a connection, rather than the idle timeout.
+ *
+ * This applies only to TCP-based DNS connections (i.e., TCPDNS or
+ * TLSDNS). On other types of connection it has no effect.
+ */
+
+isc_sockaddr_t
+isc_nmhandle_peeraddr(isc_nmhandle_t *handle);
+/*%<
+ * Return the peer address for the given handle.
+ */
+isc_sockaddr_t
+isc_nmhandle_localaddr(isc_nmhandle_t *handle);
+/*%<
+ * Return the local address for the given handle.
+ */
+
+isc_nm_t *
+isc_nmhandle_netmgr(isc_nmhandle_t *handle);
+/*%<
+ * Return a pointer to the netmgr object for the given handle.
+ */
+
+isc_result_t
+isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb,
+ void *cbarg, size_t extrasize, isc_nmsocket_t **sockp);
+/*%<
+ * Start listening for UDP packets on interface 'iface' using net manager
+ * 'mgr'.
+ *
+ * On success, 'sockp' will be updated to contain a new listening UDP socket.
+ *
+ * When a packet is received on the socket, 'cb' will be called with 'cbarg'
+ * as its argument.
+ *
+ * When handles are allocated for the socket, 'extrasize' additional bytes
+ * can be allocated along with the handle for an associated object, which
+ * can then be freed automatically when the handle is destroyed.
+ */
+
+void
+isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize);
+/*%<
+ * Open a UDP socket, bind to 'local' and connect to 'peer', and
+ * immediately call 'cb' with a handle so that the caller can begin
+ * sending packets over UDP.
+ *
+ * When handles are allocated for the socket, 'extrasize' additional bytes
+ * can be allocated along with the handle for an associated object, which
+ * can then be freed automatically when the handle is destroyed.
+ *
+ * 'timeout' specifies the timeout interval in milliseconds.
+ *
+ * The connected socket can only be accessed via the handle passed to
+ * 'cb'.
+ */
+
+isc_result_t
+isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg,
+ size_t extrahandlesize);
+/*%<
+ * Open a route/netlink socket and call 'cb', so the caller can be
+ * begin listening for interface changes. This behaves similarly to
+ * isc_nm_udpconnect().
+ *
+ * Returns ISC_R_NOTIMPLEMENTED on systems where route/netlink sockets
+ * are not supported.
+ */
+
+void
+isc_nm_stoplistening(isc_nmsocket_t *sock);
+/*%<
+ * Stop listening on socket 'sock'.
+ */
+
+void
+isc_nm_pause(isc_nm_t *mgr);
+/*%<
+ * Pause all processing, equivalent to taskmgr exclusive tasks.
+ * It won't return until all workers have been paused.
+ */
+
+void
+isc_nm_resume(isc_nm_t *mgr);
+/*%<
+ * Resume paused processing. It will return immediately after signalling
+ * workers to resume.
+ */
+
+void
+isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+/*
+ * Begin (or continue) reading on the socket associated with 'handle', and
+ * update its recv callback to 'cb', which will be called as soon as there
+ * is data to process.
+ */
+
+void
+isc_nm_pauseread(isc_nmhandle_t *handle);
+/*%<
+ * Pause reading on this handle's socket, but remember the callback.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle.
+ */
+
+void
+isc_nm_cancelread(isc_nmhandle_t *handle);
+/*%<
+ * Cancel reading on a connected socket. Calls the read/recv callback on
+ * active handles with a result code of ISC_R_CANCELED.
+ *
+ * Requires:
+ * \li 'sock' is a valid netmgr socket
+ * \li ...for which a read/recv callback has been defined.
+ */
+
+void
+isc_nm_resumeread(isc_nmhandle_t *handle);
+/*%<
+ * Resume reading on the handle's socket.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle.
+ * \li ...for a socket with a defined read/recv callback.
+ */
+
+void
+isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb,
+ void *cbarg);
+/*%<
+ * Send the data in 'region' via 'handle'. Afterward, the callback 'cb' is
+ * called with the argument 'cbarg'.
+ *
+ * 'region' is not copied; it has to be allocated beforehand and freed
+ * in 'cb'.
+ */
+
+isc_result_t
+isc_nm_listentcp(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_nmsocket_t **sockp);
+/*%<
+ * Start listening for raw messages over the TCP interface 'iface', using
+ * net manager 'mgr'.
+ *
+ * On success, 'sockp' will be updated to contain a new listening TCP
+ * socket.
+ *
+ * When connection is accepted on the socket, 'accept_cb' will be called with
+ * 'accept_cbarg' as its argument. The callback is expected to start a read.
+ *
+ * When handles are allocated for the socket, 'extrasize' additional bytes
+ * will be allocated along with the handle for an associated object.
+ *
+ * If 'quota' is not NULL, then the socket is attached to the specified
+ * quota. This allows us to enforce TCP client quota limits.
+ *
+ */
+
+void
+isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize);
+/*%<
+ * Create a socket using netmgr 'mgr', bind it to the address 'local',
+ * and connect it to the address 'peer'.
+ *
+ * When the connection is complete or has timed out, call 'cb' with
+ * argument 'cbarg'. Allocate 'extrahandlesize' additional bytes along
+ * with the handle to use for an associated object.
+ *
+ * 'timeout' specifies the timeout interval in milliseconds.
+ *
+ * The connected socket can only be accessed via the handle passed to
+ * 'cb'.
+ */
+
+isc_result_t
+isc_nm_listentcpdns(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_recv_cb_t recv_cb, void *recv_cbarg,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_nmsocket_t **sockp);
+/*%<
+ * Start listening for DNS messages over the TCP interface 'iface', using
+ * net manager 'mgr'.
+ *
+ * On success, 'sockp' will be updated to contain a new listening TCPDNS
+ * socket. This is a wrapper around a raw TCP socket, which sends and
+ * receives DNS messages via that socket. It handles message buffering
+ * and pipelining, and automatically prepends messages with a two-byte
+ * length field.
+ *
+ * When a complete DNS message is received on the socket, 'cb' will be
+ * called with 'cbarg' as its argument.
+ *
+ * When a new TCPDNS connection is accepted, 'accept_cb' will be called
+ * with 'accept_cbarg' as its argument.
+ *
+ * When handles are allocated for the socket, 'extrasize' additional bytes
+ * will be allocated along with the handle for an associated object
+ * (typically ns_client).
+ *
+ * 'quota' is passed to isc_nm_listentcp() when opening the raw TCP socket.
+ */
+
+isc_result_t
+isc_nm_listentlsdns(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_recv_cb_t recv_cb, void *recv_cbarg,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_tlsctx_t *sslctx, isc_nmsocket_t **sockp);
+/*%<
+ * Same as isc_nm_listentcpdns but for an SSL (DoT) socket.
+ */
+
+void
+isc_nm_sequential(isc_nmhandle_t *handle);
+/*%<
+ * Disable pipelining on this connection. Each DNS packet will be only
+ * processed after the previous completes.
+ *
+ * The socket must be unpaused after the query is processed. This is done
+ * the response is sent, or if we're dropping the query, it will be done
+ * when a handle is fully dereferenced by calling the socket's
+ * closehandle_cb callback.
+ *
+ * Note: This can only be run while a message is being processed; if it is
+ * run before any messages are read, no messages will be read.
+ *
+ * Also note: once this has been set, it cannot be reversed for a given
+ * connection.
+ */
+
+void
+isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle,
+ uint32_t keepalive, uint32_t advertised);
+/*%<
+ * Sets the initial, idle, and keepalive timeout values (in milliseconds) to use
+ * for TCP connections, and the timeout value to advertise in responses using
+ * the EDNS TCP Keepalive option (which should ordinarily be the same
+ * as 'keepalive'), in milliseconds.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr.
+ */
+
+void
+isc_nm_setnetbuffers(isc_nm_t *mgr, int32_t recv_tcp, int32_t send_tcp,
+ int32_t recv_udp, int32_t send_udp);
+/*%<
+ * If not 0, sets the SO_RCVBUF and SO_SNDBUF socket options for TCP and UDP
+ * respectively.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr.
+ */
+
+bool
+isc_nm_getloadbalancesockets(isc_nm_t *mgr);
+void
+isc_nm_setloadbalancesockets(isc_nm_t *mgr, bool enabled);
+/*%<
+ * Get and set value of load balancing of the sockets.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr.
+ */
+
+void
+isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle,
+ uint32_t *keepalive, uint32_t *advertised);
+/*%<
+ * Gets the initial, idle, keepalive, or advertised timeout values,
+ * in milliseconds.
+ *
+ * Any integer pointer parameter not set to NULL will be updated to
+ * contain the corresponding timeout value.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr.
+ */
+
+void
+isc_nm_maxudp(isc_nm_t *mgr, uint32_t maxudp);
+/*%<
+ * Simulate a broken firewall that blocks UDP messages larger than a given
+ * size.
+ */
+
+void
+isc_nm_setstats(isc_nm_t *mgr, isc_stats_t *stats);
+/*%<
+ * Set a socket statistics counter set 'stats' for 'mgr'.
+ *
+ * Requires:
+ *\li 'mgr' is valid and doesn't have stats already set.
+ *
+ *\li stats is a valid set of statistics counters supporting the
+ * full range of socket-related stats counter numbers.
+ */
+
+isc_result_t
+isc_nm_checkaddr(const isc_sockaddr_t *addr, isc_socktype_t type);
+/*%<
+ * Check whether the specified address is available on the local system
+ * by opening a socket and immediately closing it.
+ *
+ * Requires:
+ *\li 'addr' is not NULL.
+ */
+
+void
+isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize);
+void
+isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize, isc_tlsctx_t *sslctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache);
+/*%<
+ * Establish a DNS client connection via a TCP or TLS connection, bound to
+ * the address 'local' and connected to the address 'peer'.
+ *
+ * When the connection is complete or has timed out, call 'cb' with
+ * argument 'cbarg'. Allocate 'extrahandlesize' additional bytes along
+ * with the handle to use for an associated object.
+ *
+ * 'timeout' specifies the timeout interval in milliseconds.
+ *
+ * The connected socket can only be accessed via the handle passed to
+ * 'cb'.
+ */
+
+/*%<
+ * Returns 'true' iff 'handle' is associated with a socket of type
+ * 'isc_nm_tlsdnssocket'.
+ */
+
+bool
+isc_nm_is_http_handle(isc_nmhandle_t *handle);
+/*%<
+ * Returns 'true' iff 'handle' is associated with a socket of type
+ * 'isc_nm_httpsocket'.
+ */
+
+#if HAVE_LIBNGHTTP2
+
+#define ISC_NM_HTTP_DEFAULT_PATH "/dns-query"
+
+isc_result_t
+isc_nm_listentls(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_tlsctx_t *sslctx, isc_nmsocket_t **sockp);
+
+void
+isc_nm_tlsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, isc_tlsctx_t *ctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ unsigned int timeout, size_t extrahandlesize);
+
+void
+isc_nm_httpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ const char *uri, bool POST, isc_nm_cb_t cb, void *cbarg,
+ isc_tlsctx_t *ctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ unsigned int timeout, size_t extrahandlesize);
+
+isc_result_t
+isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog,
+ isc_quota_t *quota, isc_tlsctx_t *ctx,
+ isc_nm_http_endpoints_t *eps, uint32_t max_concurrent_streams,
+ isc_nmsocket_t **sockp);
+
+isc_nm_http_endpoints_t *
+isc_nm_http_endpoints_new(isc_mem_t *mctx);
+/*%<
+ * Create a new, empty HTTP endpoints set object.
+ *
+ * Requires:
+ * \li 'mctx' a valid memory context object.
+ */
+
+isc_result_t
+isc_nm_http_endpoints_add(isc_nm_http_endpoints_t *restrict eps,
+ const char *uri, const isc_nm_recv_cb_t cb,
+ void *cbarg, const size_t extrahandlesize);
+/*%< Adds a new endpoint to the given HTTP endpoints set object.
+ *
+ * NOTE: adding an endpoint is allowed only if the endpoint object has
+ * not been passed to isc_nm_listenhttp() yet.
+ *
+ * Requires:
+ * \li 'eps' is a valid pointer to a valid isc_nm_http_endpoints_t
+ * object;
+ * \li 'uri' is a valid pointer to a string of length > 0;
+ * \li 'cb' is a valid pointer to a read callback function.
+ */
+
+void
+isc_nm_http_endpoints_attach(isc_nm_http_endpoints_t *source,
+ isc_nm_http_endpoints_t **targetp);
+/*%<
+ * Attaches to an HTTP endpoints set object.
+ *
+ * Requires:
+ * \li 'source' is a non-NULL pointer to a valid
+ * isc_nm_http_endpoints_t object;
+ * \li 'target' is a pointer to a pointer, containing NULL.
+ */
+
+void
+isc_nm_http_endpoints_detach(isc_nm_http_endpoints_t **restrict epsp);
+/*%<
+ * Detaches from an HTTP endpoints set object. When reference count
+ * reaches 0, the object get deleted.
+ *
+ * Requires:
+ * \li 'epsp' is a pointer to a pointer to a valid
+ * isc_nm_http_endpoints_t object.
+ */
+
+bool
+isc_nm_http_path_isvalid(const char *path);
+/*%<
+ * Returns 'true' if 'path' matches the format requirements for
+ * the path component of a URI as defined in RFC 3986 section 3.3.
+ */
+
+void
+isc_nm_http_makeuri(const bool https, const isc_sockaddr_t *sa,
+ const char *hostname, const uint16_t http_port,
+ const char *abs_path, char *outbuf,
+ const size_t outbuf_len);
+/*%<
+ * Makes a URI connection string out of na isc_sockaddr_t object 'sa'
+ * or the specified 'hostname' and 'http_port'.
+ *
+ * Requires:
+ * \li 'abs_path' is a valid absolute HTTP path string;
+ * \li 'outbuf' is a valid pointer to a buffer which will get the result;
+ * \li 'outbuf_len' is a size of the result buffer and is greater than zero.
+ */
+
+void
+isc_nm_http_set_endpoints(isc_nmsocket_t *listener,
+ isc_nm_http_endpoints_t *eps);
+/*%<
+ * Asynchronously replace the set of HTTP endpoints (paths) within
+ * the listener socket object. The function is intended to be used
+ * during reconfiguration.
+ *
+ * Requires:
+ * \li 'listener' is a pointer to a valid network manager HTTP listener socket;
+ * \li 'eps' is a valid pointer to an HTTP endpoints set.
+ */
+
+#endif /* HAVE_LIBNGHTTP2 */
+
+void
+isc_nm_bad_request(isc_nmhandle_t *handle);
+/*%<
+ * Perform a transport protocol specific action on the handle in case of a
+ * bad/malformed incoming DNS message.
+ *
+ * NOTE: The function currently is no-op for any protocol except HTTP/2.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle object.
+ */
+
+isc_result_t
+isc_nm_xfr_checkperm(isc_nmhandle_t *handle);
+/*%<
+ * Check if it is permitted to do a zone transfer over the given handle.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS Success, permission check passed successfully
+ * \li #ISC_R_DOTALPNERROR No permission because of ALPN tag mismatch
+ * \li #ISC_R_NOPERM No permission because of other restrictions
+ * \li any other result indicates failure (i.e. no permission)
+ *
+ * Requires:
+ * \li 'handle' is a valid connection handle.
+ */
+
+void
+isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl);
+/*%<
+ * Set the minimal time to live from the server's response Answer
+ * section as a hint to the underlying transport.
+ *
+ * NOTE: The function currently is no-op for any protocol except HTTP/2.
+ *
+ * Requires:
+ *
+ * \li 'handle' is a valid netmgr handle object associated with an accepted
+ * connection.
+ */
+
+isc_nmsocket_type
+isc_nm_socket_type(const isc_nmhandle_t *handle);
+/*%<
+ * Returns the handle's underlying socket type.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle object.
+ */
+
+bool
+isc_nm_has_encryption(const isc_nmhandle_t *handle);
+/*%<
+ * Returns 'true' iff the handle's underlying transport does encryption.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle object.
+ */
+
+const char *
+isc_nm_verify_tls_peer_result_string(const isc_nmhandle_t *handle);
+/*%<
+ * Returns user-readable message describing TLS peer's certificate
+ * validation result. Returns 'NULL' for the transport handles for
+ * which peer verification was not performed.
+ *
+ * Requires:
+ * \li 'handle' is a valid netmgr handle object.
+ */
+
+void
+isc_nm_task_enqueue(isc_nm_t *mgr, isc_task_t *task, int threadid);
+/*%<
+ * Enqueue the 'task' onto the netmgr ievents queue.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr object
+ * \li 'task' is a valid task
+ * \li 'threadid' is either the preferred netmgr tid or -1, in which case
+ * tid will be picked randomly. The threadid is capped (by modulo) to
+ * maximum number of 'workers' as specifed in isc_nm_start()
+ */
+
+void
+isc_nm_work_offload(isc_nm_t *mgr, isc_nm_workcb_t work_cb,
+ isc_nm_after_workcb_t after_work_cb, void *data);
+/*%<
+ * Schedules a job to be handled by the libuv thread pool (see uv_work_t).
+ * The function specified in `work_cb` will be run by a thread in the
+ * thread pool; when complete, the `after_work_cb` function will run.
+ *
+ * Requires:
+ * \li 'mgr' is a valid netmgr object.
+ * \li We are currently running in a network manager thread.
+ */
+
+void
+isc__nm_force_tid(int tid);
+/*%<
+ * Force the thread ID to 'tid'. This is STRICTLY for use in unit
+ * tests and should not be used in any production code.
+ */
+
+void
+isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout);
+
+/*
+ * Timer related functions
+ */
+
+typedef struct isc_nm_timer isc_nm_timer_t;
+
+typedef void (*isc_nm_timer_cb)(void *, isc_result_t);
+
+void
+isc_nm_timer_create(isc_nmhandle_t *, isc_nm_timer_cb, void *,
+ isc_nm_timer_t **);
+
+void
+isc_nm_timer_attach(isc_nm_timer_t *, isc_nm_timer_t **);
+
+void
+isc_nm_timer_detach(isc_nm_timer_t **);
+
+void
+isc_nm_timer_start(isc_nm_timer_t *, uint64_t);
+
+void
+isc_nm_timer_stop(isc_nm_timer_t *);
diff --git a/lib/isc/include/isc/netscope.h b/lib/isc/include/isc/netscope.h
new file mode 100644
index 0000000..307864a
--- /dev/null
+++ b/lib/isc/include/isc/netscope.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 isc/netscope.h */
+
+#include <inttypes.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * Convert a string of an IPv6 scope zone to zone index. If the conversion
+ * succeeds, 'zoneid' will store the index value.
+ *
+ * XXXJT: when a standard interface for this purpose is defined,
+ * we should use it.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS: conversion succeeds
+ * \li ISC_R_FAILURE: conversion fails
+ */
+isc_result_t
+isc_netscope_pton(int af, char *scopename, void *addr, uint32_t *zoneid);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/nonce.h b/lib/isc/include/isc/nonce.h
new file mode 100644
index 0000000..b593e41
--- /dev/null
+++ b/lib/isc/include/isc/nonce.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
+
+#include <stdlib.h>
+
+#include <isc/lang.h>
+
+/*! \file isc/nonce.h
+ * \brief Provides a function for generating an arbitrarily long nonce.
+ */
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_nonce_buf(void *buf, size_t buflen);
+/*!<
+ * Fill 'buf', up to 'buflen' bytes, with random data from the
+ * crypto provider's random function.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/offset.h b/lib/isc/include/isc/offset.h
new file mode 100644
index 0000000..2b62e2a
--- /dev/null
+++ b/lib/isc/include/isc/offset.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
+ * \brief
+ * File offsets are operating-system dependent.
+ */
+#include <limits.h> /* Required for CHAR_BIT. */
+#include <stddef.h> /* For Linux Standard Base. */
+
+#include <sys/types.h>
+
+typedef off_t isc_offset_t;
diff --git a/lib/isc/include/isc/once.h b/lib/isc/include/isc/once.h
new file mode 100644
index 0000000..7d341ca
--- /dev/null
+++ b/lib/isc/include/isc/once.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
+
+/*! \file */
+
+#include <pthread.h>
+
+#include <isc/result.h>
+
+typedef pthread_once_t isc_once_t;
+
+#define ISC_ONCE_INIT PTHREAD_ONCE_INIT
+
+/* XXX We could do fancier error handling... */
+
+#define isc_once_do(op, f) \
+ ((pthread_once((op), (f)) == 0) ? ISC_R_SUCCESS : ISC_R_UNEXPECTED)
diff --git a/lib/isc/include/isc/os.h b/lib/isc/include/isc/os.h
new file mode 100644
index 0000000..fd7e5cf
--- /dev/null
+++ b/lib/isc/include/isc/os.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/os.h */
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#include <sys/stat.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%<
+ * Hardcode the L1 cacheline size of the CPU to 64, this is checked in
+ * the os.c library constructor if operating system provide means to
+ * get the L1 cacheline size using sysconf().
+ */
+#define ISC_OS_CACHELINE_SIZE 64
+
+unsigned int
+isc_os_ncpus(void);
+/*%<
+ * Return the number of CPUs available on the system, or 1 if this cannot
+ * be determined.
+ */
+
+unsigned long
+isc_os_cacheline(void);
+/*%<
+ * Return L1 cacheline size of the CPU.
+ * If L1 cache is greater than ISC_OS_CACHELINE_SIZE, ensure it is used
+ * instead of constant. Is common on ppc64le architecture.
+ */
+
+mode_t
+isc_os_umask(void);
+/*%<
+ * Return umask of the current process as initialized at the program start
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/parseint.h b/lib/isc/include/isc/parseint.h
new file mode 100644
index 0000000..aa54772
--- /dev/null
+++ b/lib/isc/include/isc/parseint.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/types.h>
+
+/*! \file isc/parseint.h
+ * \brief Parse integers, in a saner way than atoi() or strtoul() do.
+ */
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_parse_uint32(uint32_t *uip, const char *string, int base);
+
+isc_result_t
+isc_parse_uint16(uint16_t *uip, const char *string, int base);
+
+isc_result_t
+isc_parse_uint8(uint8_t *uip, const char *string, int base);
+/*%<
+ * Parse the null-terminated string 'string' containing a base 'base'
+ * integer, storing the result in '*uip'.
+ * The base is interpreted
+ * as in strtoul(). Unlike strtoul(), leading whitespace, minus or
+ * plus signs are not accepted, and all errors (including overflow)
+ * are reported uniformly through the return value.
+ *
+ * Requires:
+ *\li 'string' points to a null-terminated string
+ *\li 0 <= 'base' <= 36
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_BADNUMBER The string is not numeric (in the given base)
+ *\li #ISC_R_RANGE The number is not representable as the requested type.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/pool.h b/lib/isc/include/isc/pool.h
new file mode 100644
index 0000000..90b8934
--- /dev/null
+++ b/lib/isc/include/isc/pool.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/pool.h
+ * \brief An object pool is a mechanism for sharing a small pool of
+ * fungible objects among a large number of objects that depend on them.
+ *
+ * This is useful, for example, when it causes performance problems for
+ * large number of zones to share a single memory context or task object,
+ * but it would create a different set of problems for them each to have an
+ * independent task or memory context.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <isc/lang.h>
+#include <isc/mem.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types.
+*****/
+
+typedef void (*isc_pooldeallocator_t)(void **object);
+
+typedef isc_result_t (*isc_poolinitializer_t)(void **target, void *arg);
+
+typedef struct isc_pool isc_pool_t;
+
+/*****
+***** Functions.
+*****/
+
+isc_result_t
+isc_pool_create(isc_mem_t *mctx, unsigned int count, isc_pooldeallocator_t free,
+ isc_poolinitializer_t init, void *initarg, isc_pool_t **poolp);
+/*%<
+ * Create a pool of "count" object pointers. If 'free' is not NULL,
+ * it points to a function that will detach the objects. 'init'
+ * points to a function that will initialize the arguments, and
+ * 'arg' to an argument to be passed into that function (for example,
+ * a relevant manager or context object).
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li init != NULL
+ *
+ *\li poolp != NULL && *poolp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, '*poolp' points to the new object pool.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ */
+
+void *
+isc_pool_get(isc_pool_t *pool);
+/*%<
+ * Returns a pointer to an object from the pool. Currently the object
+ * is chosen from the pool at random. (This may be changed in the future
+ * to something that guaratees balance.)
+ */
+
+int
+isc_pool_count(isc_pool_t *pool);
+/*%<
+ * Returns the number of objcts in the pool 'pool'.
+ */
+
+isc_result_t
+isc_pool_expand(isc_pool_t **sourcep, unsigned int count, isc_pool_t **targetp);
+
+/*%<
+ * If 'size' is larger than the number of objects in the pool pointed to by
+ * 'sourcep', then a new pool of size 'count' is allocated, the existing
+ * objects are copied into it, additional ones created to bring the
+ * total number up to 'count', and the resulting pool is attached to
+ * 'targetp'.
+ *
+ * If 'count' is less than or equal to the number of objects in 'source', then
+ * 'sourcep' is attached to 'targetp' without any other action being taken.
+ *
+ * In either case, 'sourcep' is detached.
+ *
+ * Requires:
+ *
+ * \li 'sourcep' is not NULL and '*source' is not NULL
+ * \li 'targetp' is not NULL and '*source' is NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*targetp' points to a valid task pool.
+ * \li On success, '*sourcep' points to NULL.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+void
+isc_pool_destroy(isc_pool_t **poolp);
+/*%<
+ * Destroy a task pool. The tasks in the pool are detached but not
+ * shut down.
+ *
+ * Requires:
+ * \li '*poolp' is a valid task pool.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/portset.h b/lib/isc/include/isc/portset.h
new file mode 100644
index 0000000..fa5d51e
--- /dev/null
+++ b/lib/isc/include/isc/portset.h
@@ -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 isc/portset.h
+ * \brief Transport Protocol Port Manipulation Module
+ *
+ * This module provides simple utilities to handle a set of transport protocol
+ * (UDP or TCP) port numbers, e.g., for creating an ACL list. An isc_portset_t
+ * object is an opaque instance of a port set, for which the user can add or
+ * remove a specific port or a range of consecutive ports. This object is
+ * expected to be used as a temporary work space only, and does not protect
+ * simultaneous access from multiple threads. Therefore it must not be stored
+ * in a place that can be accessed from multiple threads.
+ */
+
+#pragma once
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/net.h>
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_portset_create(isc_mem_t *mctx, isc_portset_t **portsetp);
+/*%<
+ * Create a port set and initialize it as an empty set.
+ *
+ * Requires:
+ *\li 'mctx' to be valid.
+ *\li 'portsetp' to be non NULL and '*portsetp' to be NULL;
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ */
+
+void
+isc_portset_destroy(isc_mem_t *mctx, isc_portset_t **portsetp);
+/*%<
+ * Destroy a port set.
+ *
+ * Requires:
+ *\li 'mctx' to be valid and must be the same context given when the port set
+ * was created.
+ *\li '*portsetp' to be a valid set.
+ */
+
+bool
+isc_portset_isset(isc_portset_t *portset, in_port_t port);
+/*%<
+ * Test whether the given port is stored in the portset.
+ *
+ * Requires:
+ *\li 'portset' to be a valid set.
+ *
+ * Returns
+ * \li #true if the port is found, false otherwise.
+ */
+
+unsigned int
+isc_portset_nports(isc_portset_t *portset);
+/*%<
+ * Provides the number of ports stored in the given portset.
+ *
+ * Requires:
+ *\li 'portset' to be a valid set.
+ *
+ * Returns
+ * \li the number of ports stored in portset.
+ */
+
+void
+isc_portset_add(isc_portset_t *portset, in_port_t port);
+/*%<
+ * Add the given port to the portset. The port may or may not be stored in
+ * the portset.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ */
+
+void
+isc_portset_remove(isc_portset_t *portset, in_port_t port);
+/*%<
+ * Remove the given port to the portset. The port may or may not be stored in
+ * the portset.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ */
+
+void
+isc_portset_addrange(isc_portset_t *portset, in_port_t port_lo,
+ in_port_t port_hi);
+/*%<
+ * Add a subset of [port_lo, port_hi] (inclusive) to the portset. Ports in the
+ * subset may or may not be stored in portset.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li port_lo <= port_hi
+ */
+
+void
+isc_portset_removerange(isc_portset_t *portset, in_port_t port_lo,
+ in_port_t port_hi);
+/*%<
+ * Subtract a subset of [port_lo, port_hi] (inclusive) from the portset. Ports
+ * in the subset may or may not be stored in portset.
+ *
+ * Requires:
+ *\li 'portlist' to be valid.
+ *\li port_lo <= port_hi
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/print.h b/lib/isc/include/isc/print.h
new file mode 100644
index 0000000..d48bf70
--- /dev/null
+++ b/lib/isc/include/isc/print.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
+
+/*! \file isc/print.h */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/formatcheck.h> /* Required for ISC_FORMAT_PRINTF() macro. */
+#include <isc/lang.h>
+
+/***
+ *** Functions
+ ***/
+
+#include <stdio.h>
diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h
new file mode 100644
index 0000000..2a25fa1
--- /dev/null
+++ b/lib/isc/include/isc/quota.h
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/quota.h
+ *
+ * \brief The isc_quota_t object is a simple helper object for implementing
+ * quotas on things like the number of simultaneous connections to
+ * a server. It keeps track of the amount of quota in use, and
+ * encapsulates the locking necessary to allow multiple tasks to
+ * share a quota.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <isc/atomic.h>
+#include <isc/lang.h>
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+
+/*****
+***** Types.
+*****/
+
+ISC_LANG_BEGINDECLS
+
+/*% isc_quota_cb - quota callback structure */
+typedef struct isc_quota_cb isc_quota_cb_t;
+typedef void (*isc_quota_cb_func_t)(isc_quota_t *quota, void *data);
+struct isc_quota_cb {
+ int magic;
+ isc_quota_cb_func_t cb_func;
+ void *data;
+ ISC_LINK(isc_quota_cb_t) link;
+};
+
+/*% isc_quota structure */
+struct isc_quota {
+ int magic;
+ atomic_uint_fast32_t max;
+ atomic_uint_fast32_t used;
+ atomic_uint_fast32_t soft;
+ atomic_uint_fast32_t waiting;
+ isc_mutex_t cblock;
+ ISC_LIST(isc_quota_cb_t) cbs;
+ ISC_LINK(isc_quota_t) link;
+};
+
+void
+isc_quota_init(isc_quota_t *quota, unsigned int max);
+/*%<
+ * Initialize a quota object.
+ */
+
+void
+isc_quota_destroy(isc_quota_t *quota);
+/*%<
+ * Destroy a quota object.
+ */
+
+void
+isc_quota_soft(isc_quota_t *quota, unsigned int soft);
+/*%<
+ * Set a soft quota.
+ */
+
+void
+isc_quota_max(isc_quota_t *quota, unsigned int max);
+/*%<
+ * Re-set a maximum quota.
+ */
+
+unsigned int
+isc_quota_getmax(isc_quota_t *quota);
+/*%<
+ * Get the maximum quota.
+ */
+
+unsigned int
+isc_quota_getsoft(isc_quota_t *quota);
+/*%<
+ * Get the soft quota.
+ */
+
+unsigned int
+isc_quota_getused(isc_quota_t *quota);
+/*%<
+ * Get the current usage of quota.
+ */
+
+isc_result_t
+isc_quota_attach(isc_quota_t *quota, isc_quota_t **p);
+/*%<
+ *
+ * Attempt to reserve one unit of 'quota', and also attaches '*p' to the quota
+ * if successful (ISC_R_SUCCESS or ISC_R_SOFTQUOTA).
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS Success
+ * \li #ISC_R_SOFTQUOTA Success soft quota reached
+ * \li #ISC_R_QUOTA Quota is full
+ */
+
+isc_result_t
+isc_quota_attach_cb(isc_quota_t *quota, isc_quota_t **p, isc_quota_cb_t *cb);
+/*%<
+ *
+ * Like isc_quota_attach(), but if there's no quota left then cb->cb_func will
+ * be called when we are attached to quota.
+ *
+ * Note: It's the caller's responsibility to make sure that we don't end up
+ * with a huge number of callbacks waiting, making it easy to create a
+ * resource exhaustion attack. For example, in the case of TCP listening,
+ * we simply don't accept new connections when the quota is exceeded, so
+ * the number of callbacks waiting in the queue will be limited by the
+ * listen() backlog.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS Success
+ * \li #ISC_R_SOFTQUOTA Success soft quota reached
+ * \li #ISC_R_QUOTA Quota is full
+ */
+
+void
+isc_quota_cb_init(isc_quota_cb_t *cb, isc_quota_cb_func_t cb_func, void *data);
+/*%<
+ * Initialize isc_quota_cb_t - setup the list, set the callback and data.
+ */
+
+void
+isc_quota_detach(isc_quota_t **p);
+/*%<
+ * Release one unit of quota, and also detaches '*p' from the quota.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/radix.h b/lib/isc/include/isc/radix.h
new file mode 100644
index 0000000..9a91118
--- /dev/null
+++ b/lib/isc/include/isc/radix.h
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <string.h>
+
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/net.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+#define NETADDR_TO_PREFIX_T(na, pt, bits) \
+ do { \
+ const void *p = na; \
+ memset(&(pt), 0, sizeof(pt)); \
+ if (p != NULL) { \
+ (pt).family = (na)->family; \
+ (pt).bitlen = (bits); \
+ if ((pt).family == AF_INET6) { \
+ memmove(&(pt).add.sin6, &(na)->type.in6, \
+ ((bits) + 7) / 8); \
+ } else \
+ memmove(&(pt).add.sin, &(na)->type.in, \
+ ((bits) + 7) / 8); \
+ } else { \
+ (pt).family = AF_UNSPEC; \
+ (pt).bitlen = 0; \
+ } \
+ isc_refcount_init(&(pt).refcount, 0); \
+ } while (0)
+
+typedef struct isc_prefix {
+ isc_mem_t *mctx;
+ unsigned int family; /* AF_INET | AF_INET6, or AF_UNSPEC for
+ * "any" */
+ unsigned int bitlen; /* 0 for "any" */
+ isc_refcount_t refcount;
+ union {
+ struct in_addr sin;
+ struct in6_addr sin6;
+ } add;
+} isc_prefix_t;
+
+typedef void (*isc_radix_destroyfunc_t)(void *);
+typedef void (*isc_radix_processfunc_t)(isc_prefix_t *, void **);
+
+#define isc_prefix_tochar(prefix) ((char *)&(prefix)->add.sin)
+#define isc_prefix_touchar(prefix) ((u_char *)&(prefix)->add.sin)
+
+/*
+ * We need "first match" when we search the radix tree to preserve
+ * compatibility with the existing ACL implementation. Radix trees
+ * naturally lend themselves to "best match". In order to get "first match"
+ * behavior, we keep track of the order in which entries are added to the
+ * tree--and when a search is made, we find all matching entries, and
+ * return the one that was added first.
+ *
+ * An IPv4 prefix and an IPv6 prefix may share a radix tree node if they
+ * have the same length and bit pattern (e.g., 127/8 and 7f::/8). To
+ * disambiguate between them, node_num and data are two-element arrays:
+ *
+ * - node_num[0] and data[0] are used for IPv4 client addresses
+ * - node_num[1] and data[1] are used for IPv6 client addresses
+ *
+ * A prefix of 0/0 (aka "any" or "none"), is always stored as IPv4,
+ * but matches all IPv6 addresses too.
+ */
+
+#define RADIX_V4 0
+#define RADIX_V6 1
+#define RADIX_FAMILIES 2
+
+#define ISC_RADIX_FAMILY(p) (((p)->family == AF_INET6) ? RADIX_V6 : RADIX_V4)
+
+typedef struct isc_radix_node {
+ isc_mem_t *mctx;
+ uint32_t bit; /* bit length of the prefix */
+ isc_prefix_t *prefix; /* who we are in radix tree */
+ struct isc_radix_node *l, *r; /* left and right children */
+ struct isc_radix_node *parent; /* may be used */
+ void *data[RADIX_FAMILIES]; /* pointers to IPv4
+ * and IPV6 data */
+ int node_num[RADIX_FAMILIES]; /* which node
+ * this was in
+ * the tree,
+ * or -1 for glue
+ * nodes */
+} isc_radix_node_t;
+
+#define RADIX_TREE_MAGIC ISC_MAGIC('R', 'd', 'x', 'T');
+#define RADIX_TREE_VALID(a) ISC_MAGIC_VALID(a, RADIX_TREE_MAGIC);
+
+typedef struct isc_radix_tree {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_radix_node_t *head;
+ uint32_t maxbits; /* for IP, 32 bit addresses */
+ int num_active_node; /* for debugging purposes */
+ int num_added_node; /* total number of nodes */
+} isc_radix_tree_t;
+
+isc_result_t
+isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target,
+ isc_prefix_t *prefix);
+/*%<
+ * Search 'radix' for the best match to 'prefix'.
+ * Return the node found in '*target'.
+ *
+ * Requires:
+ * \li 'radix' to be valid.
+ * \li 'target' is not NULL and "*target" is NULL.
+ * \li 'prefix' to be valid.
+ *
+ * Returns:
+ * \li ISC_R_NOTFOUND
+ * \li ISC_R_SUCCESS
+ */
+
+isc_result_t
+isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
+ isc_radix_node_t *source, isc_prefix_t *prefix);
+/*%<
+ * Insert 'source' or 'prefix' into the radix tree 'radix'.
+ * Return the node added in 'target'.
+ *
+ * Requires:
+ * \li 'radix' to be valid.
+ * \li 'target' is not NULL and "*target" is NULL.
+ * \li 'prefix' to be valid or 'source' to be non NULL and contain
+ * a valid prefix.
+ *
+ * Returns:
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_SUCCESS
+ */
+
+void
+isc_radix_remove(isc_radix_tree_t *radix, isc_radix_node_t *node);
+/*%<
+ * Remove the node 'node' from the radix tree 'radix'.
+ *
+ * Requires:
+ * \li 'radix' to be valid.
+ * \li 'node' to be valid.
+ */
+
+isc_result_t
+isc_radix_create(isc_mem_t *mctx, isc_radix_tree_t **target, int maxbits);
+/*%<
+ * Create a radix tree with a maximum depth of 'maxbits';
+ *
+ * Requires:
+ * \li 'mctx' to be valid.
+ * \li 'target' to be non NULL and '*target' to be NULL.
+ * \li 'maxbits' to be less than or equal to RADIX_MAXBITS.
+ *
+ * Returns:
+ * \li ISC_R_NOMEMORY
+ * \li ISC_R_SUCCESS
+ */
+
+void
+isc_radix_destroy(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func);
+/*%<
+ * Destroy a radix tree optionally calling 'func' to clean up node data.
+ *
+ * Requires:
+ * \li 'radix' to be valid.
+ */
+
+void
+isc_radix_process(isc_radix_tree_t *radix, isc_radix_processfunc_t func);
+/*%<
+ * Walk a radix tree calling 'func' to process node data.
+ *
+ * Requires:
+ * \li 'radix' to be valid.
+ * \li 'func' to point to a function.
+ */
+
+#define RADIX_MAXBITS 128
+#define RADIX_NBIT(x) (0x80 >> ((x)&0x7f))
+#define RADIX_NBYTE(x) ((x) >> 3)
+
+#define RADIX_WALK(Xhead, Xnode) \
+ do { \
+ isc_radix_node_t *Xstack[RADIX_MAXBITS + 1]; \
+ isc_radix_node_t **Xsp = Xstack; \
+ isc_radix_node_t *Xrn = (Xhead); \
+ while ((Xnode = Xrn)) { \
+ if (Xnode->prefix)
+
+#define RADIX_WALK_END \
+ if (Xrn->l) { \
+ if (Xrn->r) { \
+ *Xsp++ = Xrn->r; \
+ } \
+ Xrn = Xrn->l; \
+ } else if (Xrn->r) { \
+ Xrn = Xrn->r; \
+ } else if (Xsp != Xstack) { \
+ Xrn = *(--Xsp); \
+ } else { \
+ Xrn = (isc_radix_node_t *)0; \
+ } \
+ } \
+ } \
+ while (0)
diff --git a/lib/isc/include/isc/random.h b/lib/isc/include/isc/random.h
new file mode 100644
index 0000000..1e30d0c
--- /dev/null
+++ b/lib/isc/include/isc/random.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 <inttypes.h>
+#include <stdlib.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*! \file isc/random.h
+ * \brief Implements wrapper around a non-cryptographically secure
+ * pseudo-random number generator.
+ *
+ */
+
+ISC_LANG_BEGINDECLS
+
+uint8_t
+isc_random8(void);
+/*!<
+ * \brief Returns a single 8-bit random value.
+ */
+
+uint16_t
+isc_random16(void);
+/*!<
+ * \brief Returns a single 16-bit random value.
+ */
+
+uint32_t
+isc_random32(void);
+/*!<
+ * \brief Returns a single 32-bit random value.
+ */
+
+void
+isc_random_buf(void *buf, size_t buflen);
+/*!<
+ * \brief Fills the region buf of length buflen with random data.
+ */
+
+uint32_t
+isc_random_uniform(uint32_t upper_bound);
+/*!<
+ * \brief Will return a single 32-bit value, uniformly distributed but
+ * less than upper_bound. This is recommended over
+ * constructions like ``isc_random() % upper_bound'' as it
+ * avoids "modulo bias" when the upper bound is not a power of
+ * two. In the worst case, this function may require multiple
+ * iterations to ensure uniformity.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/ratelimiter.h b/lib/isc/include/isc/ratelimiter.h
new file mode 100644
index 0000000..45ec447
--- /dev/null
+++ b/lib/isc/include/isc/ratelimiter.h
@@ -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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/ratelimiter.h
+ * \brief A rate limiter is a mechanism for dispatching events at a limited
+ * rate. This is intended to be used when sending zone maintenance
+ * SOA queries, NOTIFY messages, etc.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Functions.
+*****/
+
+isc_result_t
+isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
+ isc_task_t *task, isc_ratelimiter_t **ratelimiterp);
+/*%<
+ * Create a rate limiter. The execution interval is initially undefined.
+ */
+
+isc_result_t
+isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval);
+/*!<
+ * Set the minimum interval between event executions.
+ * The interval value is copied, so the caller need not preserve it.
+ *
+ * Requires:
+ * '*interval' is a nonzero interval.
+ */
+
+void
+isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, uint32_t perint);
+/*%<
+ * Set the number of events processed per interval timer tick.
+ * If 'perint' is zero it is treated as 1.
+ */
+
+void
+isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop);
+/*%<
+ * Set / clear the ratelimiter to from push pop mode rather
+ * first in - first out mode (default).
+ */
+
+isc_result_t
+isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
+ isc_event_t **eventp);
+/*%<
+ * Queue an event for rate-limited execution.
+ *
+ * This is similar
+ * to doing an isc_task_send() to the 'task', except that the
+ * execution may be delayed to achieve the desired rate of
+ * execution.
+ *
+ * '(*eventp)->ev_sender' is used to hold the task. The caller
+ * must ensure that the task exists until the event is delivered.
+ *
+ * Requires:
+ *\li An interval has been set by calling
+ * isc_ratelimiter_setinterval().
+ *
+ *\li 'task' to be non NULL.
+ *\li '(*eventp)->ev_sender' to be NULL.
+ */
+
+isc_result_t
+isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event);
+/*
+ * Dequeue a event off the ratelimiter queue.
+ *
+ * Returns:
+ * \li ISC_R_NOTFOUND if the event is no longer linked to the rate limiter.
+ * \li ISC_R_SUCCESS
+ */
+
+void
+isc_ratelimiter_shutdown(isc_ratelimiter_t *ratelimiter);
+/*%<
+ * Shut down a rate limiter.
+ *
+ * Ensures:
+ *\li All events that have not yet been
+ * dispatched to the task are dispatched immediately with
+ * the #ISC_EVENTATTR_CANCELED bit set in ev_attributes.
+ *
+ *\li Further attempts to enqueue events will fail with
+ * #ISC_R_SHUTTINGDOWN.
+ *
+ *\li The rate limiter is no longer attached to its task.
+ */
+
+void
+isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target);
+/*%<
+ * Attach to a rate limiter.
+ */
+
+void
+isc_ratelimiter_detach(isc_ratelimiter_t **ratelimiterp);
+/*%<
+ * Detach from a rate limiter.
+ */
+
+isc_result_t
+isc_ratelimiter_stall(isc_ratelimiter_t *rl);
+/*%<
+ * Stall event processing.
+ */
+
+isc_result_t
+isc_ratelimiter_release(isc_ratelimiter_t *rl);
+/*%<
+ * Release a stalled rate limiter.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/refcount.h b/lib/isc/include/isc/refcount.h
new file mode 100644
index 0000000..6b1f7cd
--- /dev/null
+++ b/lib/isc/include/isc/refcount.h
@@ -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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+#include <isc/assertions.h>
+#include <isc/atomic.h>
+#include <isc/error.h>
+#include <isc/lang.h>
+#include <isc/mutex.h>
+#include <isc/types.h>
+
+/*! \file isc/refcount.h
+ * \brief Implements a locked reference counter.
+ *
+ * These macros uses C11(-like) atomic functions to implement reference
+ * counting. The isc_refcount_t type must not be accessed directly.
+ */
+
+ISC_LANG_BEGINDECLS
+
+typedef atomic_uint_fast32_t isc_refcount_t;
+
+/** \def isc_refcount_init(ref, n)
+ * \brief Initialize the reference counter.
+ * \param[in] ref pointer to reference counter.
+ * \param[in] n an initial number of references.
+ * \return nothing.
+ *
+ * \warning No memory barrier are being imposed here.
+ */
+#define isc_refcount_init(target, value) atomic_init(target, value)
+
+/** \def isc_refcount_current(ref)
+ * \brief Returns current number of references.
+ * \param[in] ref pointer to reference counter.
+ * \returns current value of reference counter.
+ *
+ * Undo implicit promotion to 64 bits in our Windows implementation of
+ * atomic_load_explicit() by casting to uint_fast32_t.
+ */
+
+#define isc_refcount_current(target) (uint_fast32_t) atomic_load_acquire(target)
+
+/** \def isc_refcount_destroy(ref)
+ * \brief a destructor that makes sure that all references were cleared.
+ * \param[in] ref pointer to reference counter.
+ * \returns nothing.
+ */
+#define isc_refcount_destroy(target) \
+ ISC_REQUIRE(isc_refcount_current(target) == 0)
+
+/** \def isc_refcount_increment0(ref)
+ * \brief increases reference counter by 1.
+ * \param[in] ref pointer to reference counter.
+ * \returns previous value of reference counter.
+ */
+#if _MSC_VER
+static inline uint_fast32_t
+isc_refcount_increment0(isc_refcount_t *target) {
+ uint_fast32_t __v;
+ __v = (uint_fast32_t)atomic_fetch_add_relaxed(target, 1);
+ INSIST(__v < UINT32_MAX);
+ return (__v);
+}
+#else /* _MSC_VER */
+#define isc_refcount_increment0(target) \
+ ({ \
+ uint_fast32_t __v; \
+ __v = atomic_fetch_add_relaxed(target, 1); \
+ INSIST(__v < UINT32_MAX); \
+ __v; \
+ })
+#endif /* _MSC_VER */
+
+/** \def isc_refcount_increment(ref)
+ * \brief increases reference counter by 1.
+ * \param[in] ref pointer to reference counter.
+ * \returns previous value of reference counter.
+ */
+#if _MSC_VER
+static inline uint_fast32_t
+isc_refcount_increment(isc_refcount_t *target) {
+ uint_fast32_t __v;
+ __v = (uint_fast32_t)atomic_fetch_add_relaxed(target, 1);
+ INSIST(__v > 0 && __v < UINT32_MAX);
+ return (__v);
+}
+#else /* _MSC_VER */
+#define isc_refcount_increment(target) \
+ ({ \
+ uint_fast32_t __v; \
+ __v = atomic_fetch_add_relaxed(target, 1); \
+ INSIST(__v > 0 && __v < UINT32_MAX); \
+ __v; \
+ })
+#endif /* _MSC_VER */
+
+/** \def isc_refcount_decrement(ref)
+ * \brief decreases reference counter by 1.
+ * \param[in] ref pointer to reference counter.
+ * \returns previous value of reference counter.
+ */
+#if _MSC_VER
+static inline uint_fast32_t
+isc_refcount_decrement(isc_refcount_t *target) {
+ uint_fast32_t __v;
+ __v = (uint_fast32_t)atomic_fetch_sub_acq_rel(target, 1);
+ INSIST(__v > 0);
+ return (__v);
+}
+#else /* _MSC_VER */
+#define isc_refcount_decrement(target) \
+ ({ \
+ uint_fast32_t __v; \
+ __v = atomic_fetch_sub_acq_rel(target, 1); \
+ INSIST(__v > 0); \
+ __v; \
+ })
+#endif /* _MSC_VER */
+
+#define isc_refcount_decrementz(target) \
+ do { \
+ uint_fast32_t _refs = isc_refcount_decrement(target); \
+ ISC_INSIST(_refs == 1); \
+ } while (0)
+
+#define isc_refcount_decrement1(target) \
+ do { \
+ uint_fast32_t _refs = isc_refcount_decrement(target); \
+ ISC_INSIST(_refs > 1); \
+ } while (0)
+
+#define isc_refcount_decrement0(target) \
+ do { \
+ uint_fast32_t _refs = isc_refcount_decrement(target); \
+ ISC_INSIST(_refs > 0); \
+ } while (0)
+
+#define ISC_REFCOUNT_TRACE_DECL(name) \
+ name##_t *name##__ref(name##_t *ptr, const char *func, \
+ const char *file, unsigned int line); \
+ void name##__unref(name##_t *ptr, const char *func, const char *file, \
+ unsigned int line); \
+ void name##__attach(name##_t *ptr, name##_t **ptrp, const char *func, \
+ const char *file, unsigned int line); \
+ void name##__detach(name##_t **ptrp, const char *func, \
+ const char *file, unsigned int line)
+
+#define ISC_REFCOUNT_TRACE_IMPL(name, destroy) \
+ name##_t *name##__ref(name##_t *ptr, const char *func, \
+ const char *file, unsigned int line) { \
+ REQUIRE(ptr != NULL); \
+ uint_fast32_t refs = \
+ isc_refcount_increment(&ptr->references) + 1; \
+ fprintf(stderr, \
+ "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \
+ __func__, func, file, line, ptr, refs); \
+ return (ptr); \
+ } \
+ \
+ void name##__unref(name##_t *ptr, const char *func, const char *file, \
+ unsigned int line) { \
+ REQUIRE(ptr != NULL); \
+ uint_fast32_t refs = \
+ isc_refcount_decrement(&ptr->references) - 1; \
+ if (refs == 0) { \
+ destroy(ptr); \
+ } \
+ fprintf(stderr, \
+ "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \
+ __func__, func, file, line, ptr, refs); \
+ } \
+ void name##__attach(name##_t *ptr, name##_t **ptrp, const char *func, \
+ const char *file, unsigned int line) { \
+ REQUIRE(ptrp != NULL && *ptrp == NULL); \
+ uint_fast32_t refs = \
+ isc_refcount_increment(&ptr->references) + 1; \
+ fprintf(stderr, \
+ "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \
+ __func__, func, file, line, ptr, refs); \
+ *ptrp = ptr; \
+ } \
+ \
+ void name##__detach(name##_t **ptrp, const char *func, \
+ const char *file, unsigned int line) { \
+ REQUIRE(ptrp != NULL && *ptrp != NULL); \
+ name##_t *ptr = *ptrp; \
+ *ptrp = NULL; \
+ uint_fast32_t refs = \
+ isc_refcount_decrement(&ptr->references) - 1; \
+ if (refs == 0) { \
+ destroy(ptr); \
+ } \
+ fprintf(stderr, \
+ "%s:%s:%s:%u:%p->references = %" PRIuFAST32 "\n", \
+ __func__, func, file, line, ptr, refs); \
+ }
+
+#define ISC_REFCOUNT_DECL(name) \
+ name##_t *name##_ref(name##_t *ptr); \
+ void name##_unref(name##_t *ptr); \
+ void name##_attach(name##_t *ptr, name##_t **ptrp); \
+ void name##_detach(name##_t **ptrp)
+
+#define ISC_REFCOUNT_IMPL(name, destroy) \
+ name##_t *name##_ref(name##_t *ptr) { \
+ REQUIRE(ptr != NULL); \
+ isc_refcount_increment(&ptr->references); \
+ return (ptr); \
+ } \
+ \
+ void name##_unref(name##_t *ptr) { \
+ REQUIRE(ptr != NULL); \
+ if (isc_refcount_decrement(&ptr->references) == 1) { \
+ destroy(ptr); \
+ } \
+ } \
+ void name##_attach(name##_t *ptr, name##_t **ptrp) { \
+ REQUIRE(ptrp != NULL && *ptrp == NULL); \
+ name##_ref(ptr); \
+ *ptrp = ptr; \
+ } \
+ \
+ void name##_detach(name##_t **ptrp) { \
+ REQUIRE(ptrp != NULL && *ptrp != NULL); \
+ name##_t *ptr = *ptrp; \
+ *ptrp = NULL; \
+ name##_unref(ptr); \
+ }
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/regex.h b/lib/isc/include/isc/regex.h
new file mode 100644
index 0000000..989d7b1
--- /dev/null
+++ b/lib/isc/include/isc/regex.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
+
+/*! \file isc/regex.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+int
+isc_regex_validate(const char *expression);
+/*%<
+ * Check a regular expression for syntactic correctness.
+ *
+ * Returns:
+ *\li -1 on error.
+ *\li the number of groups in the expression.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/region.h b/lib/isc/include/isc/region.h
new file mode 100644
index 0000000..6f775cf
--- /dev/null
+++ b/lib/isc/include/isc/region.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/region.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+struct isc_region {
+ unsigned char *base;
+ unsigned int length;
+};
+
+struct isc_textregion {
+ char *base;
+ unsigned int length;
+};
+
+/* XXXDCL questionable ... bears discussion. we have been putting off
+ * discussing the region api.
+ */
+struct isc_constregion {
+ const void *base;
+ unsigned int length;
+};
+
+struct isc_consttextregion {
+ const char *base;
+ unsigned int length;
+};
+
+/*@{*/
+/*!
+ * The region structure is not opaque, and is usually directly manipulated.
+ * Some macros are defined below for convenience.
+ */
+
+#define isc_region_consume(r, l) \
+ do { \
+ isc_region_t *_r = (r); \
+ unsigned int _l = (l); \
+ INSIST(_r->length >= _l); \
+ _r->base += _l; \
+ _r->length -= _l; \
+ } while (0)
+
+#define isc_textregion_consume(r, l) \
+ do { \
+ isc_textregion_t *_r = (r); \
+ unsigned int _l = (l); \
+ INSIST(_r->length >= _l); \
+ _r->base += _l; \
+ _r->length -= _l; \
+ } while (0)
+
+#define isc_constregion_consume(r, l) \
+ do { \
+ isc_constregion_t *_r = (r); \
+ unsigned int _l = (l); \
+ INSIST(_r->length >= _l); \
+ _r->base += _l; \
+ _r->length -= _l; \
+ } while (0)
+/*@}*/
+
+ISC_LANG_BEGINDECLS
+
+int
+isc_region_compare(isc_region_t *r1, isc_region_t *r2);
+/*%<
+ * Compares the contents of two regions
+ *
+ * Requires:
+ *\li 'r1' is a valid region
+ *\li 'r2' is a valid region
+ *
+ * Returns:
+ *\li < 0 if r1 is lexicographically less than r2
+ *\li = 0 if r1 is lexicographically identical to r2
+ *\li > 0 if r1 is lexicographically greater than r2
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/resource.h b/lib/isc/include/isc/resource.h
new file mode 100644
index 0000000..dc4b2b1
--- /dev/null
+++ b/lib/isc/include/isc/resource.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
+
+/*! \file isc/resource.h */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#define ISC_RESOURCE_UNLIMITED ((isc_resourcevalue_t)UINT64_MAX)
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value);
+/*%<
+ * Set the maximum limit for a system resource.
+ *
+ * Notes:
+ *\li If 'value' exceeds the maximum possible on the operating system,
+ * it is silently limited to that maximum -- or to "infinity", if
+ * the operating system has that concept. #ISC_RESOURCE_UNLIMITED
+ * can be used to explicitly ask for the maximum.
+ *
+ * Requires:
+ *\li 'resource' is a valid member of the isc_resource_t enumeration.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS.
+ *\li #ISC_R_NOPERM The calling process did not have adequate permission
+ * to change the resource limit.
+ */
+
+isc_result_t
+isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value);
+/*%<
+ * Get the maximum limit for a system resource.
+ *
+ * Notes:
+ *\li 'value' is set to the maximum limit.
+ *
+ *\li #ISC_RESOURCE_UNLIMITED is the maximum value of isc_resourcevalue_t.
+ *
+ *\li On many (all?) Unix systems, RLIM_INFINITY is a valid value that is
+ * significantly less than #ISC_RESOURCE_UNLIMITED, but which in practice
+ * behaves the same.
+ *
+ *\li The current ISC libdns configuration file parser assigns a value
+ * of UINT32_MAX for a size_spec of "unlimited" and ISC_UNIT32_MAX - 1
+ * for "default", the latter of which is supposed to represent "the
+ * limit that was in force when the server started". Since these are
+ * valid values in the middle of the range of isc_resourcevalue_t,
+ * there is the possibility for confusion over what exactly those
+ * particular values are supposed to represent in a particular context --
+ * discrete integral values or generalized concepts.
+ *
+ * Requires:
+ *\li 'resource' is a valid member of the isc_resource_t enumeration.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS.
+ */
+
+isc_result_t
+isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value);
+/*%<
+ * Same as isc_resource_getlimit(), but returns the current (soft) limit.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success.
+ *\li #ISC_R_NOTIMPLEMENTED 'resource' is not a type known by the OS.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/result.h b/lib/isc/include/isc/result.h
new file mode 100644
index 0000000..a43772e
--- /dev/null
+++ b/lib/isc/include/isc/result.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
+
+/*! \file isc/result.h */
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+
+typedef enum isc_result {
+ ISC_R_SUCCESS, /*%< success */
+ ISC_R_NOMEMORY, /*%< out of memory */
+ ISC_R_TIMEDOUT, /*%< timed out */
+ ISC_R_NOTHREADS, /*%< no available threads */
+ ISC_R_ADDRNOTAVAIL, /*%< address not available */
+ ISC_R_ADDRINUSE, /*%< address in use */
+ ISC_R_NOPERM, /*%< permission denied */
+ ISC_R_NOCONN, /*%< no pending connections */
+ ISC_R_NETUNREACH, /*%< network unreachable */
+ ISC_R_HOSTUNREACH, /*%< host unreachable */
+ ISC_R_NETDOWN, /*%< network down */
+ ISC_R_HOSTDOWN, /*%< host down */
+ ISC_R_CONNREFUSED, /*%< connection refused */
+ ISC_R_NORESOURCES, /*%< not enough free resources */
+ ISC_R_EOF, /*%< end of file */
+ ISC_R_BOUND, /*%< socket already bound */
+ ISC_R_RELOAD, /*%< reload */
+ ISC_R_SUSPEND = ISC_R_RELOAD, /*%< alias of 'reload' */
+ ISC_R_LOCKBUSY, /*%< lock busy */
+ ISC_R_EXISTS, /*%< already exists */
+ ISC_R_NOSPACE, /*%< ran out of space */
+ ISC_R_CANCELED, /*%< operation canceled */
+ ISC_R_NOTBOUND, /*%< socket is not bound */
+ ISC_R_SHUTTINGDOWN, /*%< shutting down */
+ ISC_R_NOTFOUND, /*%< not found */
+ ISC_R_UNEXPECTEDEND, /*%< unexpected end of input */
+ ISC_R_FAILURE, /*%< generic failure */
+ ISC_R_IOERROR, /*%< I/O error */
+ ISC_R_NOTIMPLEMENTED, /*%< not implemented */
+ ISC_R_UNBALANCED, /*%< unbalanced parentheses */
+ ISC_R_NOMORE, /*%< no more */
+ ISC_R_INVALIDFILE, /*%< invalid file */
+ ISC_R_BADBASE64, /*%< bad base64 encoding */
+ ISC_R_UNEXPECTEDTOKEN, /*%< unexpected token */
+ ISC_R_QUOTA, /*%< quota reached */
+ ISC_R_UNEXPECTED, /*%< unexpected error */
+ ISC_R_ALREADYRUNNING, /*%< already running */
+ ISC_R_IGNORE, /*%< ignore */
+ ISC_R_MASKNONCONTIG, /*%< addr mask not contiguous */
+ ISC_R_FILENOTFOUND, /*%< file not found */
+ ISC_R_FILEEXISTS, /*%< file already exists */
+ ISC_R_NOTCONNECTED, /*%< socket is not connected */
+ ISC_R_RANGE, /*%< out of range */
+ ISC_R_NOENTROPY, /*%< out of entropy */
+ ISC_R_MULTICAST, /*%< invalid use of multicast */
+ ISC_R_NOTFILE, /*%< not a file */
+ ISC_R_NOTDIRECTORY, /*%< not a directory */
+ ISC_R_EMPTY, /*%< queue is empty */
+ ISC_R_FAMILYMISMATCH, /*%< address family mismatch */
+ ISC_R_FAMILYNOSUPPORT, /*%< AF not supported */
+ ISC_R_BADHEX, /*%< bad hex encoding */
+ ISC_R_TOOMANYOPENFILES, /*%< too many open files */
+ ISC_R_NOTBLOCKING, /*%< not blocking */
+ ISC_R_UNBALANCEDQUOTES, /*%< unbalanced quotes */
+ ISC_R_INPROGRESS, /*%< operation in progress */
+ ISC_R_CONNECTIONRESET, /*%< connection reset */
+ ISC_R_SOFTQUOTA, /*%< soft quota reached */
+ ISC_R_BADNUMBER, /*%< not a valid number */
+ ISC_R_DISABLED, /*%< disabled */
+ ISC_R_MAXSIZE, /*%< max size */
+ ISC_R_BADADDRESSFORM, /*%< invalid address format */
+ ISC_R_BADBASE32, /*%< bad base32 encoding */
+ ISC_R_UNSET, /*%< unset */
+ ISC_R_MULTIPLE, /*%< multiple */
+ ISC_R_WOULDBLOCK, /*%< would block */
+ ISC_R_COMPLETE, /*%< complete */
+ ISC_R_CRYPTOFAILURE, /*%< cryptography library failure */
+ ISC_R_DISCQUOTA, /*%< disc quota */
+ ISC_R_DISCFULL, /*%< disc full */
+ ISC_R_DEFAULT, /*%< default */
+ ISC_R_IPV4PREFIX, /*%< IPv4 prefix */
+ ISC_R_TLSERROR, /*%< TLS error */
+ ISC_R_TLSBADPEERCERT, /*%< TLS peer certificate verification failed */
+ ISC_R_HTTP2ALPNERROR, /*%< ALPN for HTTP/2 failed */
+ ISC_R_DOTALPNERROR, /*%< ALPN for DoT failed */
+ ISC_R_INVALIDPROTO, /*%< invalid protocol */
+
+ DNS_R_LABELTOOLONG,
+ DNS_R_BADESCAPE,
+ DNS_R_EMPTYLABEL,
+ DNS_R_BADDOTTEDQUAD,
+ DNS_R_INVALIDNS,
+ DNS_R_UNKNOWN,
+ DNS_R_BADLABELTYPE,
+ DNS_R_BADPOINTER,
+ DNS_R_TOOMANYHOPS,
+ DNS_R_DISALLOWED,
+ DNS_R_EXTRATOKEN,
+ DNS_R_EXTRADATA,
+ DNS_R_TEXTTOOLONG,
+ DNS_R_NOTZONETOP,
+ DNS_R_SYNTAX,
+ DNS_R_BADCKSUM,
+ DNS_R_BADAAAA,
+ DNS_R_NOOWNER,
+ DNS_R_NOTTL,
+ DNS_R_BADCLASS,
+ DNS_R_NAMETOOLONG,
+ DNS_R_PARTIALMATCH,
+ DNS_R_NEWORIGIN,
+ DNS_R_UNCHANGED,
+ DNS_R_BADTTL,
+ DNS_R_NOREDATA,
+ DNS_R_CONTINUE,
+ DNS_R_DELEGATION,
+ DNS_R_GLUE,
+ DNS_R_DNAME,
+ DNS_R_CNAME,
+ DNS_R_BADDB,
+ DNS_R_ZONECUT,
+ DNS_R_BADZONE,
+ DNS_R_MOREDATA,
+ DNS_R_UPTODATE,
+ DNS_R_TSIGVERIFYFAILURE,
+ DNS_R_TSIGERRORSET,
+ DNS_R_SIGINVALID,
+ DNS_R_SIGEXPIRED,
+ DNS_R_SIGFUTURE,
+ DNS_R_KEYUNAUTHORIZED,
+ DNS_R_INVALIDTIME,
+ DNS_R_EXPECTEDTSIG,
+ DNS_R_UNEXPECTEDTSIG,
+ DNS_R_INVALIDTKEY,
+ DNS_R_HINT,
+ DNS_R_DROP,
+ DNS_R_NOTLOADED,
+ DNS_R_NCACHENXDOMAIN,
+ DNS_R_NCACHENXRRSET,
+ DNS_R_WAIT,
+ DNS_R_NOTVERIFIEDYET,
+ DNS_R_NOIDENTITY,
+ DNS_R_NOJOURNAL,
+ DNS_R_ALIAS,
+ DNS_R_USETCP,
+ DNS_R_NOVALIDSIG,
+ DNS_R_NOVALIDNSEC,
+ DNS_R_NOTINSECURE,
+ DNS_R_UNKNOWNSERVICE,
+ DNS_R_RECOVERABLE,
+ DNS_R_UNKNOWNOPT,
+ DNS_R_UNEXPECTEDID,
+ DNS_R_SEENINCLUDE,
+ DNS_R_NOTEXACT,
+ DNS_R_BLACKHOLED,
+ DNS_R_BADALG,
+ DNS_R_METATYPE,
+ DNS_R_CNAMEANDOTHER,
+ DNS_R_SINGLETON,
+ DNS_R_HINTNXRRSET,
+ DNS_R_NOMASTERFILE,
+ DNS_R_UNKNOWNPROTO,
+ DNS_R_CLOCKSKEW,
+ DNS_R_BADIXFR,
+ DNS_R_NOTAUTHORITATIVE,
+ DNS_R_NOVALIDKEY,
+ DNS_R_OBSOLETE,
+ DNS_R_FROZEN,
+ DNS_R_UNKNOWNFLAG,
+ DNS_R_EXPECTEDRESPONSE,
+ DNS_R_NOVALIDDS,
+ DNS_R_NSISADDRESS,
+ DNS_R_REMOTEFORMERR,
+ DNS_R_TRUNCATEDTCP,
+ DNS_R_LAME,
+ DNS_R_UNEXPECTEDRCODE,
+ DNS_R_UNEXPECTEDOPCODE,
+ DNS_R_CHASEDSSERVERS,
+ DNS_R_EMPTYNAME,
+ DNS_R_EMPTYWILD,
+ DNS_R_BADBITMAP,
+ DNS_R_FROMWILDCARD,
+ DNS_R_BADOWNERNAME,
+ DNS_R_BADNAME,
+ DNS_R_DYNAMIC,
+ DNS_R_UNKNOWNCOMMAND,
+ DNS_R_MUSTBESECURE,
+ DNS_R_COVERINGNSEC,
+ DNS_R_MXISADDRESS,
+ DNS_R_DUPLICATE,
+ DNS_R_INVALIDNSEC3,
+ DNS_R_NOTPRIMARY,
+ DNS_R_BROKENCHAIN,
+ DNS_R_EXPIRED,
+ DNS_R_NOTDYNAMIC,
+ DNS_R_BADEUI,
+ DNS_R_NTACOVERED,
+ DNS_R_BADCDS,
+ DNS_R_BADCDNSKEY,
+ DNS_R_OPTERR,
+ DNS_R_BADDNSTAP,
+ DNS_R_BADTSIG,
+ DNS_R_BADSIG0,
+ DNS_R_TOOMANYRECORDS,
+ DNS_R_VERIFYFAILURE,
+ DNS_R_ATZONETOP,
+ DNS_R_NOKEYMATCH,
+ DNS_R_TOOMANYKEYS,
+ DNS_R_KEYNOTACTIVE,
+ DNS_R_NSEC3ITERRANGE,
+ DNS_R_NSEC3SALTRANGE,
+ DNS_R_NSEC3BADALG,
+ DNS_R_NSEC3RESALT,
+ DNS_R_INCONSISTENTRR,
+ DNS_R_NOALPN,
+
+ DST_R_UNSUPPORTEDALG,
+ DST_R_CRYPTOFAILURE,
+ /* compat */
+ DST_R_OPENSSLFAILURE = DST_R_CRYPTOFAILURE,
+ DST_R_NOCRYPTO,
+ DST_R_NULLKEY,
+ DST_R_INVALIDPUBLICKEY,
+ DST_R_INVALIDPRIVATEKEY,
+ DST_R_WRITEERROR,
+ DST_R_INVALIDPARAM,
+ DST_R_SIGNFAILURE,
+ DST_R_VERIFYFAILURE,
+ DST_R_NOTPUBLICKEY,
+ DST_R_NOTPRIVATEKEY,
+ DST_R_KEYCANNOTCOMPUTESECRET,
+ DST_R_COMPUTESECRETFAILURE,
+ DST_R_NORANDOMNESS,
+ DST_R_BADKEYTYPE,
+ DST_R_NOENGINE,
+ DST_R_EXTERNALKEY,
+
+ DNS_R_NOERROR,
+ DNS_R_FORMERR,
+ DNS_R_SERVFAIL,
+ DNS_R_NXDOMAIN,
+ DNS_R_NOTIMP,
+ DNS_R_REFUSED,
+ DNS_R_YXDOMAIN,
+ DNS_R_YXRRSET,
+ DNS_R_NXRRSET,
+ DNS_R_NOTAUTH,
+ DNS_R_NOTZONE,
+ DNS_R_RCODE11,
+ DNS_R_RCODE12,
+ DNS_R_RCODE13,
+ DNS_R_RCODE14,
+ DNS_R_RCODE15,
+ DNS_R_BADVERS,
+ DNS_R_BADCOOKIE = DNS_R_NOERROR + 23,
+
+ ISCCC_R_UNKNOWNVERSION,
+ ISCCC_R_SYNTAX,
+ ISCCC_R_BADAUTH,
+ ISCCC_R_EXPIRED,
+ ISCCC_R_CLOCKSKEW,
+ ISCCC_R_DUPLICATE,
+ ISCCC_R_MAXDEPTH,
+
+ ISC_R_NRESULTS, /*% The number of results. */
+ ISC_R_MAKE_ENUM_32BIT = INT32_MAX,
+} isc_result_t;
+
+ISC_LANG_BEGINDECLS
+
+const char *isc_result_totext(isc_result_t);
+/*%<
+ * Convert an isc_result_t into a string message describing the result.
+ */
+
+const char *isc_result_toid(isc_result_t);
+/*%<
+ * Convert an isc_result_t into a string identifier such as
+ * "ISC_R_SUCCESS".
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/rwlock.h b/lib/isc/include/isc/rwlock.h
new file mode 100644
index 0000000..a0d7083
--- /dev/null
+++ b/lib/isc/include/isc/rwlock.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+
+/*! \file isc/rwlock.h */
+
+#include <isc/atomic.h>
+#include <isc/condition.h>
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+typedef enum {
+ isc_rwlocktype_none = 0,
+ isc_rwlocktype_read,
+ isc_rwlocktype_write
+} isc_rwlocktype_t;
+
+#if USE_PTHREAD_RWLOCK
+#include <pthread.h>
+
+struct isc_rwlock {
+ pthread_rwlock_t rwlock;
+ atomic_bool downgrade;
+};
+
+#else /* USE_PTHREAD_RWLOCK */
+
+struct isc_rwlock {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mutex_t lock;
+ atomic_int_fast32_t spins;
+
+ /*
+ * When some atomic instructions with hardware assistance are
+ * available, rwlock will use those so that concurrent readers do not
+ * interfere with each other through mutex as long as no writers
+ * appear, massively reducing the lock overhead in the typical case.
+ *
+ * The basic algorithm of this approach is the "simple
+ * writer-preference lock" shown in the following URL:
+ * http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/rw.html
+ * but our implementation does not rely on the spin lock unlike the
+ * original algorithm to be more portable as a user space application.
+ */
+
+ /* Read or modified atomically. */
+ atomic_int_fast32_t write_requests;
+ atomic_int_fast32_t write_completions;
+ atomic_int_fast32_t cnt_and_flag;
+
+ /* Locked by lock. */
+ isc_condition_t readable;
+ isc_condition_t writeable;
+ unsigned int readers_waiting;
+
+ /* Locked by rwlock itself. */
+ atomic_uint_fast32_t write_granted;
+
+ /* Unlocked. */
+ unsigned int write_quota;
+};
+
+#endif /* USE_PTHREAD_RWLOCK */
+
+void
+isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota,
+ unsigned int write_quota);
+
+isc_result_t
+isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type);
+
+isc_result_t
+isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type);
+
+isc_result_t
+isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type);
+
+isc_result_t
+isc_rwlock_tryupgrade(isc_rwlock_t *rwl);
+
+void
+isc_rwlock_downgrade(isc_rwlock_t *rwl);
+
+void
+isc_rwlock_destroy(isc_rwlock_t *rwl);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/safe.h b/lib/isc/include/isc/safe.h
new file mode 100644
index 0000000..35e7759
--- /dev/null
+++ b/lib/isc/include/isc/safe.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/safe.h */
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+int
+isc_safe_memequal(const void *, const void *, size_t);
+
+/*%<
+ * Returns true iff. two blocks of memory are equal, otherwise
+ * false.
+ *
+ */
+
+void
+isc_safe_memwipe(void *, size_t);
+
+/*%<
+ * Clear the memory of length `len` pointed to by `ptr`.
+ *
+ * Some crypto code calls memset() on stack allocated buffers just
+ * before return so that they are wiped. Such memset() calls can be
+ * optimized away by the compiler. We provide this external non-inline C
+ * function to perform the memset operation so that the compiler cannot
+ * infer about what the function does and optimize the call away.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/serial.h b/lib/isc/include/isc/serial.h
new file mode 100644
index 0000000..b7bfa5f
--- /dev/null
+++ b/lib/isc/include/isc/serial.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
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*! \file isc/serial.h
+ * \brief Implement 32 bit serial space arithmetic comparison functions.
+ * Note: Undefined results are returned as false.
+ */
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+bool
+isc_serial_lt(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' < 'b' otherwise false.
+ */
+
+bool
+isc_serial_gt(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' > 'b' otherwise false.
+ */
+
+bool
+isc_serial_le(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' <= 'b' otherwise false.
+ */
+
+bool
+isc_serial_ge(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' >= 'b' otherwise false.
+ */
+
+bool
+isc_serial_eq(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' == 'b' otherwise false.
+ */
+
+bool
+isc_serial_ne(uint32_t a, uint32_t b);
+/*%<
+ * Return true if 'a' != 'b' otherwise false.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/siphash.h b/lib/isc/include/isc/siphash.h
new file mode 100644
index 0000000..69d4742
--- /dev/null
+++ b/lib/isc/include/isc/siphash.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.
+ */
+
+/*! \file isc/siphash.h */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+#define ISC_SIPHASH24_KEY_LENGTH 128 / 8
+#define ISC_SIPHASH24_TAG_LENGTH 64 / 8
+
+#define ISC_HALFSIPHASH24_KEY_LENGTH 64 / 8
+#define ISC_HALFSIPHASH24_TAG_LENGTH 32 / 8
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_siphash24(const uint8_t *key, const uint8_t *in, const size_t inlen,
+ uint8_t *out);
+void
+isc_halfsiphash24(const uint8_t *key, const uint8_t *in, const size_t inlen,
+ uint8_t *out);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/sockaddr.h b/lib/isc/include/isc/sockaddr.h
new file mode 100644
index 0000000..9f3986b
--- /dev/null
+++ b/lib/isc/include/isc/sockaddr.h
@@ -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.
+ */
+
+#pragma once
+
+/*! \file isc/sockaddr.h */
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/net.h>
+#include <isc/types.h>
+
+#include <sys/un.h>
+
+/*
+ * Any updates to this structure should also be applied in
+ * contrib/modules/dlz/dlz_minmal.h.
+ */
+struct isc_sockaddr {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ struct sockaddr_storage ss;
+ struct sockaddr_un sunix;
+ } type;
+ unsigned int length; /* XXXRTH beginning? */
+ ISC_LINK(struct isc_sockaddr) link;
+};
+
+#define ISC_SOCKADDR_CMPADDR \
+ 0x0001 /*%< compare the address \
+ * sin_addr/sin6_addr */
+#define ISC_SOCKADDR_CMPPORT \
+ 0x0002 /*%< compare the port \
+ * sin_port/sin6_port */
+#define ISC_SOCKADDR_CMPSCOPE \
+ 0x0004 /*%< compare the scope \
+ * sin6_scope */
+#define ISC_SOCKADDR_CMPSCOPEZERO \
+ 0x0008 /*%< when comparing scopes \
+ * zero scopes always match */
+
+ISC_LANG_BEGINDECLS
+
+bool
+isc_sockaddr_compare(const isc_sockaddr_t *a, const isc_sockaddr_t *b,
+ unsigned int flags);
+/*%<
+ * Compare the elements of the two address ('a' and 'b') as specified
+ * by 'flags' and report if they are equal or not.
+ *
+ * 'flags' is set from ISC_SOCKADDR_CMP*.
+ */
+
+bool
+isc_sockaddr_equal(const isc_sockaddr_t *a, const isc_sockaddr_t *b);
+/*%<
+ * Return true iff the socket addresses 'a' and 'b' are equal.
+ */
+
+bool
+isc_sockaddr_eqaddr(const isc_sockaddr_t *a, const isc_sockaddr_t *b);
+/*%<
+ * Return true iff the address parts of the socket addresses
+ * 'a' and 'b' are equal, ignoring the ports.
+ */
+
+bool
+isc_sockaddr_eqaddrprefix(const isc_sockaddr_t *a, const isc_sockaddr_t *b,
+ unsigned int prefixlen);
+/*%<
+ * Return true iff the most significant 'prefixlen' bits of the
+ * socket addresses 'a' and 'b' are equal, ignoring the ports.
+ * If 'b''s scope is zero then 'a''s scope will be ignored.
+ */
+
+unsigned int
+isc_sockaddr_hash(const isc_sockaddr_t *sockaddr, bool address_only);
+/*%<
+ * Return a hash value for the socket address 'sockaddr'. If 'address_only'
+ * is true, the hash value will not depend on the port.
+ *
+ * IPv6 addresses containing mapped IPv4 addresses generate the same hash
+ * value as the equivalent IPv4 address.
+ */
+
+void
+isc_sockaddr_any(isc_sockaddr_t *sockaddr);
+/*%<
+ * Return the IPv4 wildcard address.
+ */
+
+void
+isc_sockaddr_any6(isc_sockaddr_t *sockaddr);
+/*%<
+ * Return the IPv6 wildcard address.
+ */
+
+void
+isc_sockaddr_anyofpf(isc_sockaddr_t *sockaddr, int family);
+/*%<
+ * Set '*sockaddr' to the wildcard address of protocol family
+ * 'family'.
+ *
+ * Requires:
+ * \li 'family' is AF_INET or AF_INET6.
+ */
+
+void
+isc_sockaddr_fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina,
+ in_port_t port);
+/*%<
+ * Construct an isc_sockaddr_t from an IPv4 address and port.
+ */
+
+void
+isc_sockaddr_fromin6(isc_sockaddr_t *sockaddr, const struct in6_addr *ina6,
+ in_port_t port);
+/*%<
+ * Construct an isc_sockaddr_t from an IPv6 address and port.
+ */
+
+void
+isc_sockaddr_v6fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina,
+ in_port_t port);
+/*%<
+ * Construct an IPv6 isc_sockaddr_t representing a mapped IPv4 address.
+ */
+
+void
+isc_sockaddr_fromnetaddr(isc_sockaddr_t *sockaddr, const isc_netaddr_t *na,
+ in_port_t port);
+/*%<
+ * Construct an isc_sockaddr_t from an isc_netaddr_t and port.
+ */
+
+int
+isc_sockaddr_pf(const isc_sockaddr_t *sockaddr);
+/*%<
+ * Get the protocol family of 'sockaddr'.
+ *
+ * Requires:
+ *
+ *\li 'sockaddr' is a valid sockaddr with an address family of AF_INET
+ * or AF_INET6.
+ *
+ * Returns:
+ *
+ *\li The protocol family of 'sockaddr', e.g. PF_INET or PF_INET6.
+ */
+
+void
+isc_sockaddr_setport(isc_sockaddr_t *sockaddr, in_port_t port);
+/*%<
+ * Set the port of 'sockaddr' to 'port'.
+ */
+
+in_port_t
+isc_sockaddr_getport(const isc_sockaddr_t *sockaddr);
+/*%<
+ * Get the port stored in 'sockaddr'.
+ */
+
+isc_result_t
+isc_sockaddr_totext(const isc_sockaddr_t *sockaddr, isc_buffer_t *target);
+/*%<
+ * Append a text representation of 'sockaddr' to the buffer 'target'.
+ * The text will include both the IP address (v4 or v6) and the port.
+ * The text is null terminated, but the terminating null is not
+ * part of the buffer's used region.
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS
+ * \li ISC_R_NOSPACE The text or the null termination did not fit.
+ */
+
+void
+isc_sockaddr_format(const isc_sockaddr_t *sa, char *array, unsigned int size);
+/*%<
+ * Format a human-readable representation of the socket address '*sa'
+ * into the character array 'array', which is of size 'size'.
+ * The resulting string is guaranteed to be null-terminated.
+ */
+
+bool
+isc_sockaddr_ismulticast(const isc_sockaddr_t *sa);
+/*%<
+ * Returns #true if the address is a multicast address.
+ */
+
+bool
+isc_sockaddr_isexperimental(const isc_sockaddr_t *sa);
+/*
+ * Returns true if the address is a experimental (CLASS E) address.
+ */
+
+bool
+isc_sockaddr_islinklocal(const isc_sockaddr_t *sa);
+/*%<
+ * Returns true if the address is a link local address.
+ */
+
+bool
+isc_sockaddr_issitelocal(const isc_sockaddr_t *sa);
+/*%<
+ * Returns true if the address is a sitelocal address.
+ */
+
+bool
+isc_sockaddr_isnetzero(const isc_sockaddr_t *sa);
+/*%<
+ * Returns true if the address is in net zero.
+ */
+
+isc_result_t
+isc_sockaddr_frompath(isc_sockaddr_t *sockaddr, const char *path);
+/*
+ * Create a UNIX domain sockaddr that refers to path.
+ *
+ * Returns:
+ * \li ISC_R_NOSPACE
+ * \li ISC_R_NOTIMPLEMENTED
+ * \li ISC_R_SUCCESS
+ */
+
+isc_result_t
+isc_sockaddr_fromsockaddr(isc_sockaddr_t *isa, const struct sockaddr *sa);
+
+#define ISC_SOCKADDR_FORMATSIZE \
+ sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:XXX.XXX.XXX.XXX%SSSSSSSSSS#" \
+ "YYYYY")
+/*%<
+ * Minimum size of array to pass to isc_sockaddr_format().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/stat.h b/lib/isc/include/isc/stat.h
new file mode 100644
index 0000000..fad37c9
--- /dev/null
+++ b/lib/isc/include/isc/stat.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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
+*****/
+
+/*
+ * Portable <sys/stat.h> support.
+ *
+ * This module is responsible for defining S_IS??? macros.
+ *
+ * MP:
+ * No impact.
+ *
+ * Reliability:
+ * No anticipated impact.
+ *
+ * Resources:
+ * N/A.
+ *
+ * Security:
+ * No anticipated impact.
+ *
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <sys/stat.h>
+#include <sys/types.h>
diff --git a/lib/isc/include/isc/stats.h b/lib/isc/include/isc/stats.h
new file mode 100644
index 0000000..5bed7d5
--- /dev/null
+++ b/lib/isc/include/isc/stats.h
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/stats.h */
+
+#include <inttypes.h>
+
+#include <isc/types.h>
+
+/*%
+ * Statistics counters. Used as isc_statscounter_t values.
+ */
+enum {
+ /*%
+ * Socket statistics counters.
+ */
+ isc_sockstatscounter_udp4open = 0,
+ isc_sockstatscounter_udp6open = 1,
+ isc_sockstatscounter_tcp4open = 2,
+ isc_sockstatscounter_tcp6open = 3,
+ isc_sockstatscounter_unixopen = 4,
+
+ isc_sockstatscounter_udp4openfail = 5,
+ isc_sockstatscounter_udp6openfail = 6,
+ isc_sockstatscounter_tcp4openfail = 7,
+ isc_sockstatscounter_tcp6openfail = 8,
+ isc_sockstatscounter_unixopenfail = 9,
+
+ isc_sockstatscounter_udp4close = 10,
+ isc_sockstatscounter_udp6close = 11,
+ isc_sockstatscounter_tcp4close = 12,
+ isc_sockstatscounter_tcp6close = 13,
+ isc_sockstatscounter_unixclose = 14,
+ isc_sockstatscounter_fdwatchclose = 15,
+
+ isc_sockstatscounter_udp4bindfail = 16,
+ isc_sockstatscounter_udp6bindfail = 17,
+ isc_sockstatscounter_tcp4bindfail = 18,
+ isc_sockstatscounter_tcp6bindfail = 19,
+ isc_sockstatscounter_unixbindfail = 20,
+ isc_sockstatscounter_fdwatchbindfail = 21,
+
+ isc_sockstatscounter_udp4connect = 22,
+ isc_sockstatscounter_udp6connect = 23,
+ isc_sockstatscounter_tcp4connect = 24,
+ isc_sockstatscounter_tcp6connect = 25,
+ isc_sockstatscounter_unixconnect = 26,
+ isc_sockstatscounter_fdwatchconnect = 27,
+
+ isc_sockstatscounter_udp4connectfail = 28,
+ isc_sockstatscounter_udp6connectfail = 29,
+ isc_sockstatscounter_tcp4connectfail = 30,
+ isc_sockstatscounter_tcp6connectfail = 31,
+ isc_sockstatscounter_unixconnectfail = 32,
+ isc_sockstatscounter_fdwatchconnectfail = 33,
+
+ isc_sockstatscounter_tcp4accept = 34,
+ isc_sockstatscounter_tcp6accept = 35,
+ isc_sockstatscounter_unixaccept = 36,
+
+ isc_sockstatscounter_tcp4acceptfail = 37,
+ isc_sockstatscounter_tcp6acceptfail = 38,
+ isc_sockstatscounter_unixacceptfail = 39,
+
+ isc_sockstatscounter_udp4sendfail = 40,
+ isc_sockstatscounter_udp6sendfail = 41,
+ isc_sockstatscounter_tcp4sendfail = 42,
+ isc_sockstatscounter_tcp6sendfail = 43,
+ isc_sockstatscounter_unixsendfail = 44,
+ isc_sockstatscounter_fdwatchsendfail = 45,
+
+ isc_sockstatscounter_udp4recvfail = 46,
+ isc_sockstatscounter_udp6recvfail = 47,
+ isc_sockstatscounter_tcp4recvfail = 48,
+ isc_sockstatscounter_tcp6recvfail = 49,
+ isc_sockstatscounter_unixrecvfail = 50,
+ isc_sockstatscounter_fdwatchrecvfail = 51,
+
+ isc_sockstatscounter_udp4active = 52,
+ isc_sockstatscounter_udp6active = 53,
+ isc_sockstatscounter_tcp4active = 54,
+ isc_sockstatscounter_tcp6active = 55,
+ isc_sockstatscounter_unixactive = 56,
+
+ isc_sockstatscounter_rawopen = 57,
+ isc_sockstatscounter_rawopenfail = 58,
+ isc_sockstatscounter_rawclose = 59,
+ isc_sockstatscounter_rawrecvfail = 60,
+ isc_sockstatscounter_rawactive = 61,
+
+ isc_sockstatscounter_max = 62
+};
+
+ISC_LANG_BEGINDECLS
+
+/*%<
+ * Flag(s) for isc_stats_dump().
+ */
+#define ISC_STATSDUMP_VERBOSE 0x00000001 /*%< dump 0-value counters */
+
+/*%<
+ * Dump callback type.
+ */
+typedef void (*isc_stats_dumper_t)(isc_statscounter_t, uint64_t, void *);
+
+isc_result_t
+isc_stats_create(isc_mem_t *mctx, isc_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.
+ *
+ * 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
+isc_stats_attach(isc_stats_t *stats, isc_stats_t **statsp);
+/*%<
+ * Attach to a statistics set.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *
+ *\li 'statsp' != NULL && '*statsp' == NULL
+ */
+
+void
+isc_stats_detach(isc_stats_t **statsp);
+/*%<
+ * Detaches from the statistics set.
+ *
+ * Requires:
+ *\li 'statsp' != NULL and '*statsp' is a valid isc_stats_t.
+ */
+
+int
+isc_stats_ncounters(isc_stats_t *stats);
+/*%<
+ * Returns the number of counters contained in stats.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *
+ */
+
+void
+isc_stats_increment(isc_stats_t *stats, isc_statscounter_t counter);
+/*%<
+ * Increment the counter-th counter of stats.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *
+ *\li counter is less than the maximum available ID for the stats specified
+ * on creation.
+ */
+
+void
+isc_stats_decrement(isc_stats_t *stats, isc_statscounter_t counter);
+/*%<
+ * Decrement the counter-th counter of stats.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ */
+
+void
+isc_stats_dump(isc_stats_t *stats, isc_stats_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.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ */
+
+void
+isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter);
+/*%<
+ * Set the given counter to the specified value.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ */
+
+void
+isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter);
+/*%<
+ * Set the given counter to the specified value.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ */
+
+void
+isc_stats_update_if_greater(isc_stats_t *stats, isc_statscounter_t counter,
+ isc_statscounter_t value);
+/*%<
+ * Atomically assigns 'value' to 'counter' if value > counter.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *
+ *\li counter is less than the maximum available ID for the stats specified
+ * on creation.
+ */
+
+isc_statscounter_t
+isc_stats_get_counter(isc_stats_t *stats, isc_statscounter_t counter);
+/*%<
+ * Returns value currently stored in counter.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *
+ *\li counter is less than the maximum available ID for the stats specified
+ * on creation.
+ */
+
+void
+isc_stats_resize(isc_stats_t **stats, int ncounters);
+/*%<
+ * Resize a statistics counter structure of general type. The new set of
+ * counters are indexed by an ID between 0 and ncounters -1.
+ *
+ * Requires:
+ *\li 'stats' is a valid isc_stats_t.
+ *\li 'ncounters' is a non-zero positive number.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/stdatomic.h b/lib/isc/include/isc/stdatomic.h
new file mode 100644
index 0000000..7c2e21c
--- /dev/null
+++ b/lib/isc/include/isc/stdatomic.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <stddef.h>
+#if HAVE_UCHAR_H
+#include <uchar.h>
+#endif /* HAVE_UCHAR_H */
+
+/* GCC 4.7.0 introduced __atomic builtins, but not the __GNUC_ATOMICS define */
+#if !defined(__GNUC_ATOMICS) && __GNUC__ == 4 && __GNUC_MINOR__ >= 7
+#define __GNUC_ATOMICS
+#endif
+
+#if !defined(__GNUC_ATOMICS)
+#error "isc/stdatomic.h does not support your compiler"
+#endif /* if !defined(__GNUC_ATOMICS) */
+
+typedef enum memory_order {
+ memory_order_relaxed = __ATOMIC_RELAXED,
+ memory_order_consume = __ATOMIC_CONSUME,
+ memory_order_acquire = __ATOMIC_ACQUIRE,
+ memory_order_release = __ATOMIC_RELEASE,
+ memory_order_acq_rel = __ATOMIC_ACQ_REL,
+ memory_order_seq_cst = __ATOMIC_SEQ_CST
+} memory_order;
+
+#ifndef HAVE_UCHAR_H
+typedef uint_least16_t char16_t;
+typedef uint_least32_t char32_t;
+#endif /* HAVE_UCHAR_H */
+
+typedef bool atomic_bool;
+typedef char atomic_char;
+typedef signed char atomic_schar;
+typedef unsigned char atomic_uchar;
+typedef short atomic_short;
+typedef unsigned short atomic_ushort;
+typedef int atomic_int;
+typedef unsigned int atomic_uint;
+typedef long atomic_long;
+typedef unsigned long atomic_ulong;
+typedef long long atomic_llong;
+typedef unsigned long long atomic_ullong;
+typedef char16_t atomic_char16_t;
+typedef char32_t atomic_char32_t;
+typedef wchar_t atomic_wchar_t;
+typedef int_least8_t atomic_int_least8_t;
+typedef uint_least8_t atomic_uint_least8_t;
+typedef int_least16_t atomic_int_least16_t;
+typedef uint_least16_t atomic_uint_least16_t;
+typedef int_least32_t atomic_int_least32_t;
+typedef uint_least32_t atomic_uint_least32_t;
+typedef int_least64_t atomic_int_least64_t;
+typedef uint_least64_t atomic_uint_least64_t;
+typedef int_fast8_t atomic_int_fast8_t;
+typedef uint_fast8_t atomic_uint_fast8_t;
+typedef int_fast16_t atomic_int_fast16_t;
+typedef uint_fast16_t atomic_uint_fast16_t;
+typedef int_fast32_t atomic_int_fast32_t;
+typedef uint_fast32_t atomic_uint_fast32_t;
+typedef int_fast64_t atomic_int_fast64_t;
+typedef uint_fast64_t atomic_uint_fast64_t;
+typedef intptr_t atomic_intptr_t;
+typedef uintptr_t atomic_uintptr_t;
+typedef size_t atomic_size_t;
+typedef ptrdiff_t atomic_ptrdiff_t;
+typedef intmax_t atomic_intmax_t;
+typedef uintmax_t atomic_uintmax_t;
+
+#define atomic_init(obj, desired) (*obj = desired)
+#define atomic_load_explicit(obj, order) __atomic_load_n(obj, order)
+#define atomic_store_explicit(obj, desired, order) \
+ __atomic_store_n(obj, desired, order)
+#define atomic_fetch_add_explicit(obj, arg, order) \
+ __atomic_fetch_add(obj, arg, order)
+#define atomic_fetch_sub_explicit(obj, arg, order) \
+ __atomic_fetch_sub(obj, arg, order)
+#define atomic_fetch_and_explicit(obj, arg, order) \
+ __atomic_fetch_and(obj, arg, order)
+#define atomic_fetch_or_explicit(obj, arg, order) \
+ __atomic_fetch_or(obj, arg, order)
+#define atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, \
+ fail) \
+ __atomic_compare_exchange_n(obj, expected, desired, 0, succ, fail)
+#define atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, \
+ fail) \
+ __atomic_compare_exchange_n(obj, expected, desired, 1, succ, fail)
+#define atomic_exchange_explicit(obj, desired, order) \
+ __atomic_exchange_n(obj, desired, order)
+
+#define atomic_load(obj) atomic_load_explicit(obj, memory_order_seq_cst)
+#define atomic_store(obj, arg) \
+ atomic_store_explicit(obj, arg, memory_order_seq_cst)
+#define atomic_fetch_add(obj, arg) \
+ atomic_fetch_add_explicit(obj, arg, memory_order_seq_cst)
+#define atomic_fetch_sub(obj, arg) \
+ atomic_fetch_sub_explicit(obj, arg, memory_order_seq_cst)
+#define atomic_fetch_and(obj, arg) \
+ atomic_fetch_and_explicit(obj, arg, memory_order_seq_cst)
+#define atomic_fetch_or(obj, arg) \
+ atomic_fetch_or_explicit(obj, arg, memory_order_seq_cst)
+#define atomic_compare_exchange_strong(obj, expected, desired) \
+ atomic_compare_exchange_strong_explicit(obj, expected, desired, \
+ memory_order_seq_cst, \
+ memory_order_seq_cst)
+#define atomic_compare_exchange_weak(obj, expected, desired) \
+ atomic_compare_exchange_weak_explicit(obj, expected, desired, \
+ memory_order_seq_cst, \
+ memory_order_seq_cst)
+#define atomic_exchange(obj, desired) \
+ atomic_exchange_explicit(obj, desired, memory_order_seq_cst)
diff --git a/lib/isc/include/isc/stdio.h b/lib/isc/include/isc/stdio.h
new file mode 100644
index 0000000..c8bac4d
--- /dev/null
+++ b/lib/isc/include/isc/stdio.h
@@ -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.
+ */
+
+#pragma once
+
+/*! \file isc/stdio.h */
+
+/*%
+ * These functions are wrappers around the corresponding stdio functions.
+ *
+ * They return a detailed error code in the form of an an isc_result_t. ANSI C
+ * does not guarantee that stdio functions set errno, hence these functions
+ * must use platform dependent methods (e.g., the POSIX errno) to construct the
+ * error code.
+ */
+
+#include <stdio.h>
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+ISC_LANG_BEGINDECLS
+
+/*% Open */
+isc_result_t
+isc_stdio_open(const char *filename, const char *mode, FILE **fp);
+
+/*% Close */
+isc_result_t
+isc_stdio_close(FILE *f);
+
+/*% Seek */
+isc_result_t
+isc_stdio_seek(FILE *f, off_t offset, int whence);
+
+/*% Tell */
+isc_result_t
+isc_stdio_tell(FILE *f, off_t *offsetp);
+
+/*% Read */
+isc_result_t
+isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, size_t *nret);
+
+/*% Write */
+isc_result_t
+isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f,
+ size_t *nret);
+
+/*% Flush */
+isc_result_t
+isc_stdio_flush(FILE *f);
+
+isc_result_t
+isc_stdio_sync(FILE *f);
+/*%<
+ * Invoke fsync() on the file descriptor underlying an stdio stream, or an
+ * equivalent system-dependent operation. Note that this function has no
+ * direct counterpart in the stdio library.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/stdtime.h b/lib/isc/include/isc/stdtime.h
new file mode 100644
index 0000000..45ec50f
--- /dev/null
+++ b/lib/isc/include/isc/stdtime.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <inttypes.h>
+#include <stdlib.h>
+
+#include <isc/lang.h>
+
+/*%
+ * It's public information that 'isc_stdtime_t' is an unsigned integral type.
+ * Applications that want maximum portability should not assume anything
+ * about its size.
+ */
+typedef uint32_t isc_stdtime_t;
+
+ISC_LANG_BEGINDECLS
+/* */
+void
+isc_stdtime_get(isc_stdtime_t *t);
+/*%<
+ * Set 't' to the number of seconds since 00:00:00 UTC, January 1, 1970.
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ */
+
+void
+isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen);
+/*
+ * Convert 't' into a null-terminated string of the form
+ * "Wed Jun 30 21:49:08 1993". Store the string in the 'out'
+ * buffer.
+ *
+ * Requires:
+ *
+ * 't' is a valid time.
+ * 'out' is a valid pointer.
+ * 'outlen' is at least 26.
+ */
+
+#define isc_stdtime_convert32(t, t32p) (*(t32p) = t)
+/*
+ * Convert the standard time to its 32-bit version.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/strerr.h b/lib/isc/include/isc/strerr.h
new file mode 100644
index 0000000..563ff48
--- /dev/null
+++ b/lib/isc/include/isc/strerr.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
+
+/*! \file isc/strerr.h */
+
+#include <isc/string.h>
+
+/***
+ *** Default strerror_r buffer size
+ ***/
+
+#define ISC_STRERRORSIZE 128
+
+#if defined(strerror_r)
+#undef strerror_r
+#endif /* if defined(strerror_r) */
+#define strerror_r isc_string_strerror_r
diff --git a/lib/isc/include/isc/string.h b/lib/isc/include/isc/string.h
new file mode 100644
index 0000000..fbf6129
--- /dev/null
+++ b/lib/isc/include/isc/string.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isc/string.h */
+
+#include <string.h>
+
+#include <isc/lang.h>
+
+ISC_LANG_BEGINDECLS
+
+#if !defined(HAVE_STRLCPY)
+size_t
+strlcpy(char *dst, const char *src, size_t size);
+#endif /* !defined(HAVE_STRLCPY) */
+
+#if !defined(HAVE_STRLCAT)
+size_t
+strlcat(char *dst, const char *src, size_t size);
+#endif /* if !defined(HAVE_STRLCAT) */
+
+#if !defined(HAVE_STRNSTR)
+char *
+strnstr(const char *s, const char *find, size_t slen);
+#endif /* if !defined(HAVE_STRNSTR) */
+
+int
+isc_string_strerror_r(int errnum, char *buf, size_t buflen);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/symtab.h b/lib/isc/include/isc/symtab.h
new file mode 100644
index 0000000..2be3a8b
--- /dev/null
+++ b/lib/isc/include/isc/symtab.h
@@ -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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/symtab.h
+ * \brief Provides a simple memory-based symbol table.
+ *
+ * Keys are C strings, and key comparisons are case-insensitive. A type may
+ * be specified when looking up, defining, or undefining. A type value of
+ * 0 means "match any type"; any other value will only match the given
+ * type.
+ *
+ * It's possible that a client will attempt to define a <key, type, value>
+ * tuple when a tuple with the given key and type already exists in the table.
+ * What to do in this case is specified by the client. Possible policies are:
+ *
+ *\li #isc_symexists_reject Disallow the define, returning #ISC_R_EXISTS
+ *\li #isc_symexists_replace Replace the old value with the new. The
+ * undefine action (if provided) will be called
+ * with the old <key, type, value> tuple.
+ *\li #isc_symexists_add Add the new tuple, leaving the old tuple in
+ * the table. Subsequent lookups will retrieve
+ * the most-recently-defined tuple.
+ *
+ * A lookup of a key using type 0 will return the most-recently defined
+ * symbol with that key. An undefine of a key using type 0 will undefine the
+ * most-recently defined symbol with that key. Trying to define a key with
+ * type 0 is illegal.
+ *
+ * The symbol table library does not make a copy the key field, so the
+ * caller must ensure that any key it passes to isc_symtab_define() will not
+ * change until it calls isc_symtab_undefine() or isc_symtab_destroy().
+ *
+ * A user-specified action will be called (if provided) when a symbol is
+ * undefined. It can be used to free memory associated with keys and/or
+ * values.
+ *
+ * A symbol table is implemented as a hash table of lists; the size of the
+ * hash table is set by the 'size' parameter to isc_symtbl_create(). When
+ * the number of entries in the symbol table reaches three quarters of this
+ * value, the hash table is reallocated with size doubled, in order to
+ * optimize lookup performance. This has a negative effect on insertion
+ * performance, which can be mitigated by sizing the table appropriately
+ * when creating it.
+ *
+ * \li MP:
+ * The callers of this module must ensure any required synchronization.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * TBS
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+/*
+ *** Symbol Tables.
+ ***/
+/*% Symbol table value. */
+typedef union isc_symvalue {
+ void *as_pointer;
+ const void *as_cpointer;
+ int as_integer;
+ unsigned int as_uinteger;
+} isc_symvalue_t;
+
+typedef void (*isc_symtabaction_t)(char *key, unsigned int type,
+ isc_symvalue_t value, void *userarg);
+/*% Symbol table exists. */
+typedef enum {
+ isc_symexists_reject = 0, /*%< Disallow the define */
+ isc_symexists_replace = 1, /*%< Replace the old value with the new */
+ isc_symexists_add = 2 /*%< Add the new tuple */
+} isc_symexists_t;
+
+ISC_LANG_BEGINDECLS
+
+/*% Create a symbol table. */
+isc_result_t
+isc_symtab_create(isc_mem_t *mctx, unsigned int size,
+ isc_symtabaction_t undefine_action, void *undefine_arg,
+ bool case_sensitive, isc_symtab_t **symtabp);
+
+/*% Destroy a symbol table. */
+void
+isc_symtab_destroy(isc_symtab_t **symtabp);
+
+/*% Lookup a symbol table. */
+isc_result_t
+isc_symtab_lookup(isc_symtab_t *symtab, const char *key, unsigned int type,
+ isc_symvalue_t *value);
+
+/*% Define a symbol table. */
+isc_result_t
+isc_symtab_define(isc_symtab_t *symtab, const char *key, unsigned int type,
+ isc_symvalue_t value, isc_symexists_t exists_policy);
+
+/*% Undefine a symbol table. */
+isc_result_t
+isc_symtab_undefine(isc_symtab_t *symtab, const char *key, unsigned int type);
+
+/*% Return the number of items in a symbol table. */
+unsigned int
+isc_symtab_count(isc_symtab_t *symtab);
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/syslog.h b/lib/isc/include/isc/syslog.h
new file mode 100644
index 0000000..021f7fa
--- /dev/null
+++ b/lib/isc/include/isc/syslog.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isc_syslog_facilityfromstring(const char *str, int *facilityp);
+/*%<
+ * Convert 'str' to the appropriate syslog facility constant.
+ *
+ * Requires:
+ *
+ *\li 'str' is not NULL
+ *\li 'facilityp' is not NULL
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOTFOUND
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/task.h b/lib/isc/include/isc/task.h
new file mode 100644
index 0000000..75b7cdb
--- /dev/null
+++ b/lib/isc/include/isc/task.h
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/task.h
+ * \brief The task system provides a lightweight execution context, which is
+ * basically an event queue.
+ *
+ * When a task's event queue is non-empty, the
+ * task is runnable. A small work crew of threads, typically one per CPU,
+ * execute runnable tasks by dispatching the events on the tasks' event
+ * queues. Context switching between tasks is fast.
+ *
+ * \li MP:
+ * The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ * The caller must ensure that isc_taskmgr_destroy() is called only
+ * once for a given manager.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * TBS
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ *
+ * \section purge Purging and Unsending
+ *
+ * Events which have been queued for a task but not delivered may be removed
+ * from the task's event queue by purging or unsending.
+ *
+ * With both types, the caller specifies a matching pattern that selects
+ * events based upon their sender, type, and tag.
+ *
+ * Purging calls isc_event_free() on the matching events.
+ *
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/eventclass.h>
+#include <isc/lang.h>
+#include <isc/netmgr.h>
+#include <isc/stdtime.h>
+#include <isc/types.h>
+
+#define ISC_TASKEVENT_FIRSTEVENT (ISC_EVENTCLASS_TASK + 0)
+#define ISC_TASKEVENT_SHUTDOWN (ISC_EVENTCLASS_TASK + 1)
+#define ISC_TASKEVENT_TEST (ISC_EVENTCLASS_TASK + 1)
+#define ISC_TASKEVENT_LASTEVENT (ISC_EVENTCLASS_TASK + 65535)
+
+/*****
+ ***** Tasks.
+ *****/
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+typedef enum {
+ isc_taskmgrmode_normal = 0,
+ isc_taskmgrmode_privileged
+} isc_taskmgrmode_t;
+
+isc_result_t
+isc_task_create(isc_taskmgr_t *manager, unsigned int quantum,
+ isc_task_t **taskp);
+isc_result_t
+isc_task_create_bound(isc_taskmgr_t *manager, unsigned int quantum,
+ isc_task_t **taskp, int threadid);
+/*%<
+ * Create a task, optionally bound to a particular threadid.
+ *
+ * Notes:
+ *
+ *\li If 'quantum' is non-zero, then only that many events can be dispatched
+ * before the task must yield to other tasks waiting to execute. If
+ * quantum is zero, then the default quantum of the task manager will
+ * be used.
+ *
+ *\li The 'quantum' option may be removed from isc_task_create() in the
+ * future. If this happens, isc_task_getquantum() and
+ * isc_task_setquantum() will be provided.
+ *
+ * Requires:
+ *
+ *\li 'manager' is a valid task manager.
+ *
+ *\li taskp != NULL && *taskp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, '*taskp' is bound to the new task.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ *\li #ISC_R_SHUTTINGDOWN
+ */
+
+void
+isc_task_ready(isc_task_t *task);
+/*%<
+ * Enqueue the task onto netmgr queue.
+ */
+
+isc_result_t
+isc_task_run(isc_task_t *task);
+/*%<
+ * Run all the queued events for the 'task', returning
+ * when the queue is empty or the number of events executed
+ * exceeds the 'quantum' specified when the task was created.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_QUOTA
+ */
+
+void
+isc_task_attach(isc_task_t *source, isc_task_t **targetp);
+/*%<
+ * Attach *targetp to source.
+ *
+ * Requires:
+ *
+ *\li 'source' is a valid task.
+ *
+ *\li 'targetp' points to a NULL isc_task_t *.
+ *
+ * Ensures:
+ *
+ *\li *targetp is attached to source.
+ */
+
+void
+isc_task_detach(isc_task_t **taskp);
+/*%<
+ * Detach *taskp from its task.
+ *
+ * Requires:
+ *
+ *\li '*taskp' is a valid task.
+ *
+ * Ensures:
+ *
+ *\li *taskp is NULL.
+ *
+ *\li If '*taskp' is the last reference to the task, the task is idle (has
+ * an empty event queue), and has not been shutdown, the task will be
+ * shutdown.
+ *
+ *\li If '*taskp' is the last reference to the task and
+ * the task has been shutdown,
+ * all resources used by the task will be freed.
+ */
+
+void
+isc_task_send(isc_task_t *task, isc_event_t **eventp);
+
+void
+isc_task_sendto(isc_task_t *task, isc_event_t **eventp, int c);
+/*%<
+ * Send '*event' to 'task', if task is idle try starting it on cpu 'c'
+ * If 'c' is smaller than 0 then cpu is selected randomly.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *\li eventp != NULL && *eventp != NULL.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ */
+
+void
+isc_task_sendtoanddetach(isc_task_t **taskp, isc_event_t **eventp, int c);
+
+void
+isc_task_sendanddetach(isc_task_t **taskp, isc_event_t **eventp);
+/*%<
+ * Send '*event' to '*taskp' and then detach '*taskp' from its
+ * task. If task is idle try starting it on cpu 'c'
+ * If 'c' is smaller than 0 then cpu is selected randomly.
+ *
+ * Requires:
+ *
+ *\li '*taskp' is a valid task.
+ *\li eventp != NULL && *eventp != NULL.
+ *
+ * Ensures:
+ *
+ *\li *eventp == NULL.
+ *
+ *\li *taskp == NULL.
+ *
+ *\li If '*taskp' is the last reference to the task, the task is
+ * idle (has an empty event queue), and has not been shutdown,
+ * the task will be shutdown.
+ *
+ *\li If '*taskp' is the last reference to the task and
+ * the task has been shutdown,
+ * all resources used by the task will be freed.
+ */
+
+unsigned int
+isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first,
+ isc_eventtype_t last, void *tag);
+/*%<
+ * Purge events from a task's event queue.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li last >= first
+ *
+ * Ensures:
+ *
+ *\li Events in the event queue of 'task' whose sender is 'sender', whose
+ * type is >= first and <= last, and whose tag is 'tag' will be purged,
+ * unless they are marked as unpurgable.
+ *
+ *\li A sender of NULL will match any sender. A NULL tag matches any
+ * tag.
+ *
+ * Returns:
+ *
+ *\li The number of events purged.
+ */
+
+unsigned int
+isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag);
+/*%<
+ * Purge events from a task's event queue.
+ *
+ * Notes:
+ *
+ *\li This function is equivalent to
+ *
+ *\code
+ * isc_task_purgerange(task, sender, type, type, tag);
+ *\endcode
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ * Ensures:
+ *
+ *\li Events in the event queue of 'task' whose sender is 'sender', whose
+ * type is 'type', and whose tag is 'tag' will be purged, unless they
+ * are marked as unpurgable.
+ *
+ *\li A sender of NULL will match any sender. A NULL tag matches any
+ * tag.
+ *
+ * Returns:
+ *
+ *\li The number of events purged.
+ */
+
+bool
+isc_task_purgeevent(isc_task_t *task, isc_event_t *event);
+/*%<
+ * Purge 'event' from a task's event queue.
+ *
+ * XXXRTH: WARNING: This method may be removed before beta.
+ *
+ * Notes:
+ *
+ *\li If 'event' is on the task's event queue, it will be purged,
+ * unless it is marked as unpurgeable. 'event' does not have to be
+ * on the task's event queue; in fact, it can even be an invalid
+ * pointer. Purging only occurs if the event is actually on the task's
+ * event queue.
+ *
+ * \li Purging never changes the state of the task.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ * Ensures:
+ *
+ *\li 'event' is not in the event queue for 'task'.
+ *
+ * Returns:
+ *
+ *\li #true The event was purged.
+ *\li #false The event was not in the event queue,
+ * or was marked unpurgeable.
+ */
+
+unsigned int
+isc_task_unsend(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag,
+ isc_eventlist_t *events);
+/*%<
+ * Remove events from a task's event queue.
+ *
+ * Notes:
+ *
+ *\li This function is equivalent to
+ *
+ *\code
+ * isc_task_unsendrange(task, sender, type, type, tag, events);
+ *\endcode
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li *events is a valid list.
+ *
+ * Ensures:
+ *
+ *\li Events in the event queue of 'task' whose sender is 'sender', whose
+ * type is 'type', and whose tag is 'tag' will be dequeued and appended
+ * to *events.
+ *
+ * Returns:
+ *
+ *\li The number of events unsent.
+ */
+
+isc_result_t
+isc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg);
+/*%<
+ * Send a shutdown event with action 'action' and argument 'arg' when
+ * 'task' is shutdown.
+ *
+ * Notes:
+ *
+ *\li Shutdown events are posted in LIFO order.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ *\li 'action' is a valid task action.
+ *
+ * Ensures:
+ *
+ *\li When the task is shutdown, shutdown events requested with
+ * isc_task_onshutdown() will be appended to the task's event queue.
+ *
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_SHUTTINGDOWN Task is shutting down.
+ */
+
+void
+isc_task_shutdown(isc_task_t *task);
+/*%<
+ * Shutdown 'task'.
+ *
+ * Notes:
+ *
+ *\li Shutting down a task causes any shutdown events requested with
+ * isc_task_onshutdown() to be posted (in LIFO order). The task
+ * moves into a "shutting down" mode which prevents further calls
+ * to isc_task_onshutdown().
+ *
+ *\li Trying to shutdown a task that has already been shutdown has no
+ * effect.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ *
+ * Ensures:
+ *
+ *\li Any shutdown events requested with isc_task_onshutdown() have been
+ * posted (in LIFO order).
+ */
+
+void
+isc_task_destroy(isc_task_t **taskp);
+/*%<
+ * Destroy '*taskp'.
+ *
+ * Notes:
+ *
+ *\li This call is equivalent to:
+ *
+ *\code
+ * isc_task_shutdown(*taskp);
+ * isc_task_detach(taskp);
+ *\endcode
+ *
+ * Requires:
+ *
+ * '*taskp' is a valid task.
+ *
+ * Ensures:
+ *
+ *\li Any shutdown events requested with isc_task_onshutdown() have been
+ * posted (in LIFO order).
+ *
+ *\li *taskp == NULL
+ *
+ *\li If '*taskp' is the last reference to the task,
+ * all resources used by the task will be freed.
+ */
+
+void
+isc_task_setname(isc_task_t *task, const char *name, void *tag);
+/*%<
+ * Name 'task'.
+ *
+ * Notes:
+ *
+ *\li Only the first 15 characters of 'name' will be copied.
+ *
+ *\li Naming a task is currently only useful for debugging purposes.
+ *
+ * Requires:
+ *
+ *\li 'task' is a valid task.
+ */
+
+const char *
+isc_task_getname(isc_task_t *task);
+/*%<
+ * Get the name of 'task', as previously set using isc_task_setname().
+ *
+ * Notes:
+ *\li This function is for debugging purposes only.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ *
+ * Returns:
+ *\li A non-NULL pointer to a null-terminated string.
+ * If the task has not been named, the string is
+ * empty.
+ *
+ */
+
+isc_nm_t *
+isc_task_getnetmgr(isc_task_t *task);
+
+void *
+isc_task_gettag(isc_task_t *task);
+/*%<
+ * Get the tag value for 'task', as previously set using isc_task_settag().
+ *
+ * Notes:
+ *\li This function is for debugging purposes only.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ */
+
+void
+isc_task_setquantum(isc_task_t *task, unsigned int quantum);
+/*%<
+ * Set future 'task' quantum to 'quantum'. The current 'task' quantum will be
+ * kept for the current isc_task_run() loop, and will be changed for the next
+ * run. Therefore, the function is save to use from the event callback as it
+ * will not affect the current event loop processing.
+ */
+
+isc_result_t
+isc_task_beginexclusive(isc_task_t *task);
+/*%<
+ * Request exclusive access for 'task', which must be the calling
+ * task. Waits for any other concurrently executing tasks to finish their
+ * current event, and prevents any new events from executing in any of the
+ * tasks sharing a task manager with 'task'.
+ * It also pauses processing of network events in netmgr if it was provided
+ * when taskmgr was created.
+ *
+ * The exclusive access must be relinquished by calling
+ * isc_task_endexclusive() before returning from the current event handler.
+ *
+ * Requires:
+ *\li 'task' is the calling task.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS The current task now has exclusive access.
+ *\li #ISC_R_LOCKBUSY Another task has already requested exclusive
+ * access.
+ */
+
+void
+isc_task_endexclusive(isc_task_t *task);
+/*%<
+ * Relinquish the exclusive access obtained by isc_task_beginexclusive(),
+ * allowing other tasks to execute.
+ *
+ * Requires:
+ *\li 'task' is the calling task, and has obtained
+ * exclusive access by calling isc_task_spl().
+ */
+
+bool
+isc_task_exiting(isc_task_t *t);
+/*%<
+ * Returns true if the task is in the process of shutting down,
+ * false otherwise.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ */
+
+void
+isc_task_setprivilege(isc_task_t *task, bool priv);
+/*%<
+ * Set or unset the task's "privileged" flag depending on the value of
+ * 'priv'.
+ *
+ * Under normal circumstances this flag has no effect on the task behavior,
+ * but when the task manager has been set to privileged execution mode via
+ * isc_taskmgr_setmode(), only tasks with the flag set will be executed,
+ * and all other tasks will wait until they're done. Once all privileged
+ * tasks have finished executing, the task manager resumes running
+ * non-privileged tasks.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ */
+
+bool
+isc_task_getprivilege(isc_task_t *task);
+/*%<
+ * Returns the current value of the task's privilege flag.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ */
+
+bool
+isc_task_privileged(isc_task_t *task);
+/*%<
+ * Returns true if the task's privilege flag is set *and* the task
+ * manager is currently in privileged mode.
+ *
+ * Requires:
+ *\li 'task' is a valid task.
+ */
+
+/*****
+ ***** Task Manager.
+ *****/
+
+void
+isc_taskmgr_attach(isc_taskmgr_t *, isc_taskmgr_t **);
+void
+isc_taskmgr_detach(isc_taskmgr_t **);
+/*%<
+ * Attach/detach the task manager.
+ */
+
+void
+isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode);
+isc_taskmgrmode_t
+isc_taskmgr_mode(isc_taskmgr_t *manager);
+/*%<
+ * Set/get the operating mode of the task manager. Valid modes are:
+ *
+ *\li isc_taskmgrmode_normal
+ *\li isc_taskmgrmode_privileged
+ *
+ * In privileged execution mode, only tasks that have had the "privilege"
+ * flag set via isc_task_setprivilege() can be executed. When all such
+ * tasks are complete, non-privileged tasks resume running. The task calling
+ * this function should be in task-exclusive mode; the privileged tasks
+ * will be run after isc_task_endexclusive() is called.
+ *
+ * Requires:
+ *
+ *\li 'manager' is a valid task manager.
+ */
+
+void
+isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task);
+/*%<
+ * Set a task which will be used for all task-exclusive operations.
+ *
+ * Requires:
+ *\li 'manager' is a valid task manager.
+ *
+ *\li 'task' is a valid task.
+ */
+
+isc_result_t
+isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp);
+/*%<
+ * Attach '*taskp' to the task set by isc_taskmgr_getexcltask().
+ * This task should be used whenever running in task-exclusive mode,
+ * so as to prevent deadlock between two exclusive tasks.
+ *
+ * Requires:
+ *\li 'manager' is a valid task manager.
+ *
+ *\li taskp != NULL && *taskp == NULL
+ */
+
+#ifdef HAVE_LIBXML2
+int
+isc_taskmgr_renderxml(isc_taskmgr_t *mgr, void *writer0);
+#endif /* ifdef HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+isc_result_t
+isc_taskmgr_renderjson(isc_taskmgr_t *mgr, void *tasksobj0);
+#endif /* HAVE_JSON_C */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/taskpool.h b/lib/isc/include/isc/taskpool.h
new file mode 100644
index 0000000..cde2287
--- /dev/null
+++ b/lib/isc/include/isc/taskpool.h
@@ -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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isc/taskpool.h
+ * \brief A task pool is a mechanism for sharing a small number of tasks
+ * among a large number of objects such that each object is
+ * assigned a unique task, but each task may be shared by several
+ * objects.
+ *
+ * Task pools are used to let objects that can exist in large
+ * numbers (e.g., zones) use tasks for synchronization without
+ * the memory overhead and unfair scheduling competition that
+ * could result from creating a separate task for each object.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/task.h>
+
+ISC_LANG_BEGINDECLS
+
+/*****
+***** Types.
+*****/
+
+typedef struct isc_taskpool isc_taskpool_t;
+
+/*****
+***** Functions.
+*****/
+
+isc_result_t
+isc_taskpool_create(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks,
+ unsigned int quantum, bool priv, isc_taskpool_t **poolp);
+/*%<
+ * Create a task pool of "ntasks" tasks, each with quantum
+ * "quantum".
+ *
+ * Requires:
+ *
+ *\li 'tmgr' is a valid task manager.
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li poolp != NULL && *poolp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, '*taskp' points to the new task pool.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_UNEXPECTED
+ */
+
+void
+isc_taskpool_gettask(isc_taskpool_t *pool, isc_task_t **targetp);
+/*%<
+ * Attach to a task from the pool. Currently the next task is chosen
+ * from the pool at random. (This may be changed in the future to
+ * something that guaratees balance.)
+ */
+
+int
+isc_taskpool_size(isc_taskpool_t *pool);
+/*%<
+ * Returns the number of tasks in the task pool 'pool'.
+ */
+
+isc_result_t
+isc_taskpool_expand(isc_taskpool_t **sourcep, unsigned int size, bool priv,
+ isc_taskpool_t **targetp);
+
+/*%<
+ * If 'size' is larger than the number of tasks in the pool pointed to by
+ * 'sourcep', then a new taskpool of size 'size' is allocated, the existing
+ * tasks from are moved into it, additional tasks are created to bring the
+ * total number up to 'size', and the resulting pool is attached to
+ * 'targetp'.
+ *
+ * If 'size' is less than or equal to the tasks in pool 'source', then
+ * 'sourcep' is attached to 'targetp' without any other action being taken.
+ *
+ * In either case, 'sourcep' is detached.
+ *
+ * Requires:
+ *
+ * \li 'sourcep' is not NULL and '*source' is not NULL
+ * \li 'targetp' is not NULL and '*source' is NULL
+ *
+ * Ensures:
+ *
+ * \li On success, '*targetp' points to a valid task pool.
+ * \li On success, '*sourcep' points to NULL.
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS
+ * \li #ISC_R_NOMEMORY
+ */
+
+void
+isc_taskpool_destroy(isc_taskpool_t **poolp);
+/*%<
+ * Destroy a task pool. The tasks in the pool are detached but not
+ * shut down.
+ *
+ * Requires:
+ * \li '*poolp' is a valid task pool.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/thread.h b/lib/isc/include/isc/thread.h
new file mode 100644
index 0000000..a70e2c7
--- /dev/null
+++ b/lib/isc/include/isc/thread.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <pthread.h>
+#if HAVE_THREADS_H
+#include <threads.h>
+#endif
+
+#if defined(HAVE_PTHREAD_NP_H)
+#include <pthread_np.h>
+#endif /* if defined(HAVE_PTHREAD_NP_H) */
+
+#include <isc/lang.h>
+#include <isc/result.h>
+
+extern thread_local size_t isc_tid_v;
+
+ISC_LANG_BEGINDECLS
+
+typedef pthread_t isc_thread_t;
+typedef void *isc_threadresult_t;
+typedef void *isc_threadarg_t;
+typedef isc_threadresult_t (*isc_threadfunc_t)(isc_threadarg_t);
+
+void
+isc_thread_create(isc_threadfunc_t, isc_threadarg_t, isc_thread_t *);
+
+void
+isc_thread_join(isc_thread_t thread, isc_threadresult_t *result);
+
+void
+isc_thread_yield(void);
+
+void
+isc_thread_setname(isc_thread_t thread, const char *name);
+
+#define isc_thread_self (uintptr_t) pthread_self
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/time.h b/lib/isc/include/isc/time.h
new file mode 100644
index 0000000..9c9da37
--- /dev/null
+++ b/lib/isc/include/isc/time.h
@@ -0,0 +1,468 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+enum {
+ MS_PER_SEC = 1000, /*%< Milliseonds per second. */
+ US_PER_MS = 1000, /*%< Microseconds per millisecond. */
+ US_PER_SEC = 1000 * 1000, /*%< Microseconds per second. */
+ NS_PER_US = 1000, /*%< Nanoseconds per millisecond. */
+ NS_PER_MS = 1000 * 1000, /*%< Nanoseconds per microsecond. */
+ NS_PER_SEC = 1000 * 1000 * 1000, /*%< Nanoseconds per second. */
+};
+
+/***
+ *** Intervals
+ ***/
+
+/*!
+ * \brief
+ * The contents of this structure are private, and MUST NOT be accessed
+ * directly by callers.
+ *
+ * The contents are exposed only to allow callers to avoid dynamic allocation.
+ */
+struct isc_interval {
+ unsigned int seconds;
+ unsigned int nanoseconds;
+};
+
+extern const isc_interval_t *const isc_interval_zero;
+
+/*
+ * ISC_FORMATHTTPTIMESTAMP_SIZE needs to be 30 in C locale and potentially
+ * more for other locales to handle longer national abbreviations when
+ * expanding strftime's %a and %b.
+ */
+#define ISC_FORMATHTTPTIMESTAMP_SIZE 50
+
+ISC_LANG_BEGINDECLS
+
+void
+isc_interval_set(isc_interval_t *i, unsigned int seconds,
+ unsigned int nanoseconds);
+/*%<
+ * Set 'i' to a value representing an interval of 'seconds' seconds and
+ * 'nanoseconds' nanoseconds, suitable for use in isc_time_add() and
+ * isc_time_subtract().
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ *\li nanoseconds < 1000000000.
+ */
+
+bool
+isc_interval_iszero(const isc_interval_t *i);
+/*%<
+ * Returns true iff. 'i' is the zero interval.
+ *
+ * Requires:
+ *
+ *\li 'i' is a valid pointer.
+ */
+
+unsigned int
+isc_interval_ms(const isc_interval_t *i);
+/*%<
+ * Returns interval 'i' expressed as a number of milliseconds.
+ *
+ * Requires:
+ *
+ *\li 'i' is a valid pointer.
+ */
+
+/***
+ *** Absolute Times
+ ***/
+
+/*%
+ * The contents of this structure are private, and MUST NOT be accessed
+ * directly by callers.
+ *
+ * The contents are exposed only to allow callers to avoid dynamic allocation.
+ */
+
+struct isc_time {
+ unsigned int seconds;
+ unsigned int nanoseconds;
+};
+
+extern const isc_time_t *const isc_time_epoch;
+
+void
+isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds);
+/*%<
+ * Set 't' to a value which represents the given number of seconds and
+ * nanoseconds since 00:00:00 January 1, 1970, UTC.
+ *
+ * Notes:
+ *\li The Unix version of this call is equivalent to:
+ *\code
+ * isc_time_settoepoch(t);
+ * isc_interval_set(i, seconds, nanoseconds);
+ * isc_time_add(t, i, t);
+ *\endcode
+ *
+ * Requires:
+ *\li 't' is a valid pointer.
+ *\li nanoseconds < 1000000000.
+ */
+
+void
+isc_time_settoepoch(isc_time_t *t);
+/*%<
+ * Set 't' to the time of the epoch.
+ *
+ * Notes:
+ *\li The date of the epoch is platform-dependent.
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ */
+
+bool
+isc_time_isepoch(const isc_time_t *t);
+/*%<
+ * Returns true iff. 't' is the epoch ("time zero").
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ */
+
+isc_result_t
+isc_time_now(isc_time_t *t);
+/*%<
+ * Set 't' to the current absolute time.
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li Unexpected error
+ * Getting the time from the system failed.
+ *\li Out of range
+ * The time from the system is too large to be represented
+ * in the current definition of isc_time_t.
+ */
+
+isc_result_t
+isc_time_now_hires(isc_time_t *t);
+/*%<
+ * Set 't' to the current absolute time. Uses higher resolution clocks
+ * recommended when microsecond accuracy is required.
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li Unexpected error
+ * Getting the time from the system failed.
+ *\li Out of range
+ * The time from the system is too large to be represented
+ * in the current definition of isc_time_t.
+ */
+
+isc_result_t
+isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i);
+/*%<
+ * Set *t to the current absolute time + i.
+ *
+ * Note:
+ *\li This call is equivalent to:
+ *
+ *\code
+ * isc_time_now(t);
+ * isc_time_add(t, i, t);
+ *\endcode
+ *
+ * Requires:
+ *
+ *\li 't' and 'i' are valid pointers.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li Unexpected error
+ * Getting the time from the system failed.
+ *\li Out of range
+ * The interval added to the time from the system is too large to
+ * be represented in the current definition of isc_time_t.
+ */
+
+int
+isc_time_compare(const isc_time_t *t1, const isc_time_t *t2);
+/*%<
+ * Compare the times referenced by 't1' and 't2'
+ *
+ * Requires:
+ *
+ *\li 't1' and 't2' are valid pointers.
+ *
+ * Returns:
+ *
+ *\li -1 t1 < t2 (comparing times, not pointers)
+ *\li 0 t1 = t2
+ *\li 1 t1 > t2
+ */
+
+isc_result_t
+isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result);
+/*%<
+ * Add 'i' to 't', storing the result in 'result'.
+ *
+ * Requires:
+ *
+ *\li 't', 'i', and 'result' are valid pointers.
+ *
+ * Returns:
+ *\li Success
+ *\li Out of range
+ * The interval added to the time is too large to
+ * be represented in the current definition of isc_time_t.
+ */
+
+isc_result_t
+isc_time_subtract(const isc_time_t *t, const isc_interval_t *i,
+ isc_time_t *result);
+/*%<
+ * Subtract 'i' from 't', storing the result in 'result'.
+ *
+ * Requires:
+ *
+ *\li 't', 'i', and 'result' are valid pointers.
+ *
+ * Returns:
+ *\li Success
+ *\li Out of range
+ * The interval is larger than the time since the epoch.
+ */
+
+uint64_t
+isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2);
+/*%<
+ * Find the difference in microseconds between time t1 and time t2.
+ * t2 is the subtrahend of t1; ie, difference = t1 - t2.
+ *
+ * Requires:
+ *
+ *\li 't1' and 't2' are valid pointers.
+ *
+ * Returns:
+ *\li The difference of t1 - t2, or 0 if t1 <= t2.
+ */
+
+uint32_t
+isc_time_seconds(const isc_time_t *t);
+/*%<
+ * Return the number of seconds since the epoch stored in a time structure.
+ *
+ * Requires:
+ *
+ *\li 't' is a valid pointer.
+ */
+
+isc_result_t
+isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp);
+/*%<
+ * Ensure the number of seconds in an isc_time_t is representable by a time_t.
+ *
+ * Notes:
+ *\li The number of seconds stored in an isc_time_t might be larger
+ * than the number of seconds a time_t is able to handle. Since
+ * time_t is mostly opaque according to the ANSI/ISO standard
+ * (essentially, all you can be sure of is that it is an arithmetic type,
+ * not even necessarily integral), it can be tricky to ensure that
+ * the isc_time_t is in the range a time_t can handle. Use this
+ * function in place of isc_time_seconds() any time you need to set a
+ * time_t from an isc_time_t.
+ *
+ * Requires:
+ *\li 't' is a valid pointer.
+ *
+ * Returns:
+ *\li Success
+ *\li Out of range
+ */
+
+uint32_t
+isc_time_nanoseconds(const isc_time_t *t);
+/*%<
+ * Return the number of nanoseconds stored in a time structure.
+ *
+ * Notes:
+ *\li This is the number of nanoseconds in excess of the number
+ * of seconds since the epoch; it will always be less than one
+ * full second.
+ *
+ * Requires:
+ *\li 't' is a valid pointer.
+ *
+ * Ensures:
+ *\li The returned value is less than 1*10^9.
+ */
+
+void
+isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using a format like "30-Aug-2000 04:06:47.997" and the local time zone.
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using a format like "Mon, 30 Aug 2000 04:06:47 GMT"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+isc_result_t
+isc_time_parsehttptimestamp(char *input, isc_time_t *t);
+/*%<
+ * Parse the time in 'input' into the isc_time_t pointed to by 't',
+ * expecting a format like "Mon, 30 Aug 2000 04:06:47 GMT"
+ *
+ * Requires:
+ *\li 'buf' and 't' are not NULL.
+ */
+
+void
+isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sss"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssss"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ssZ"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.sssZ"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the ISO8601 format: "yyyy-mm-ddThh:mm:ss.ssssssZ"
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf, unsigned int len);
+/*%<
+ * Format the time 't' into the buffer 'buf' of length 'len',
+ * using the format "yyyymmddhhmmsssss" useful for file timestamping.
+ * If the text does not fit in the buffer, the result is indeterminate,
+ * but is always guaranteed to be null terminated.
+ *
+ * Requires:
+ *\li 'len' > 0
+ *\li 'buf' points to an array of at least len chars
+ *
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/timer.h b/lib/isc/include/isc/timer.h
new file mode 100644
index 0000000..815cf42
--- /dev/null
+++ b/lib/isc/include/isc/timer.h
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isc/timer.h
+ * \brief Provides timers which are event sources in the task system.
+ *
+ * Three types of timers are supported:
+ *
+ *\li 'ticker' timers generate a periodic tick event.
+ *
+ *\li 'once' timers generate an idle timeout event if they are idle for too
+ * long, and generate a life timeout event if their lifetime expires.
+ * They are used to implement both (possibly expiring) idle timers and
+ * 'one-shot' timers.
+ *
+ *\li 'limited' timers generate a periodic tick event until they reach
+ * their lifetime when they generate a life timeout event.
+ *
+ *\li 'inactive' timers generate no events.
+ *
+ * Timers can change type. It is typical to create a timer as
+ * an 'inactive' timer and then change it into a 'ticker' or
+ * 'once' timer.
+ *
+ *\li MP:
+ * The module ensures appropriate synchronization of data structures it
+ * creates and manipulates.
+ * Clients of this module must not be holding a timer's task's lock when
+ * making a call that affects that timer. Failure to follow this rule
+ * can result in deadlock.
+ * The caller must ensure that isc_timermgr_destroy() is called only
+ * once for a given manager.
+ *
+ * \li Reliability:
+ * No anticipated impact.
+ *
+ * \li Resources:
+ * TBS
+ *
+ * \li Security:
+ * No anticipated impact.
+ *
+ * \li Standards:
+ * None.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/event.h>
+#include <isc/eventclass.h>
+#include <isc/lang.h>
+#include <isc/time.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Types
+ ***/
+
+/*% Timer Type */
+typedef enum {
+ isc_timertype_undefined = -1, /*%< Undefined */
+ isc_timertype_ticker = 0, /*%< Ticker */
+ isc_timertype_once = 1, /*%< Once */
+ isc_timertype_limited = 2, /*%< Limited */
+ isc_timertype_inactive = 3 /*%< Inactive */
+} isc_timertype_t;
+
+typedef struct isc_timerevent isc_timerevent_t;
+
+struct isc_timerevent {
+ struct isc_event common;
+ isc_time_t due;
+ ISC_LINK(isc_timerevent_t) ev_timerlink;
+};
+
+#define ISC_TIMEREVENT_FIRSTEVENT (ISC_EVENTCLASS_TIMER + 0)
+#define ISC_TIMEREVENT_TICK (ISC_EVENTCLASS_TIMER + 1)
+#define ISC_TIMEREVENT_IDLE (ISC_EVENTCLASS_TIMER + 2)
+#define ISC_TIMEREVENT_LIFE (ISC_EVENTCLASS_TIMER + 3)
+#define ISC_TIMEREVENT_LASTEVENT (ISC_EVENTCLASS_TIMER + 65535)
+
+/***
+ *** Timer and Timer Manager Functions
+ ***
+ *** Note: all Ensures conditions apply only if the result is success for
+ *** those functions which return an isc_result_t.
+ ***/
+
+isc_result_t
+isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type,
+ const isc_time_t *expires, const isc_interval_t *interval,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ isc_timer_t **timerp);
+/*%<
+ * Create a new 'type' timer managed by 'manager'. The timers parameters
+ * are specified by 'expires' and 'interval'. Events will be posted to
+ * 'task' and when dispatched 'action' will be called with 'arg' as the
+ * arg value. The new timer is returned in 'timerp'.
+ *
+ * Notes:
+ *
+ *\li For ticker timers, the timer will generate a 'tick' event every
+ * 'interval' seconds. The value of 'expires' is ignored.
+ *
+ *\li For once timers, 'expires' specifies the time when a life timeout
+ * event should be generated. If 'expires' is 0 (the epoch), then no life
+ * timeout will be generated. 'interval' specifies how long the timer
+ * can be idle before it generates an idle timeout. If 0, then no
+ * idle timeout will be generated.
+ *
+ *\li If 'expires' is NULL, the epoch will be used.
+ *
+ * If 'interval' is NULL, the zero interval will be used.
+ *
+ * Requires:
+ *
+ *\li 'manager' is a valid manager
+ *
+ *\li 'task' is a valid task
+ *
+ *\li 'action' is a valid action
+ *
+ *\li 'expires' points to a valid time, or is NULL.
+ *
+ *\li 'interval' points to a valid interval, or is NULL.
+ *
+ *\li type == isc_timertype_inactive ||
+ * ('expires' and 'interval' are not both 0)
+ *
+ *\li 'timerp' is a valid pointer, and *timerp == NULL
+ *
+ * Ensures:
+ *
+ *\li '*timerp' is attached to the newly created timer
+ *
+ *\li The timer is attached to the task
+ *
+ *\li An idle timeout will not be generated until at least Now + the
+ * timer's interval if 'timer' is a once timer with a non-zero
+ * interval.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li No memory
+ *\li Unexpected error
+ */
+
+isc_result_t
+isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
+ const isc_time_t *expires, const isc_interval_t *interval,
+ bool purge);
+/*%<
+ * Change the timer's type, expires, and interval values to the given
+ * values. If 'purge' is TRUE, any pending events from this timer
+ * are purged from its task's event queue.
+ *
+ * Notes:
+ *
+ *\li If 'expires' is NULL, the epoch will be used.
+ *
+ *\li If 'interval' is NULL, the zero interval will be used.
+ *
+ * Requires:
+ *
+ *\li 'timer' is a valid timer
+ *
+ *\li The same requirements that isc_timer_create() imposes on 'type',
+ * 'expires' and 'interval' apply.
+ *
+ * Ensures:
+ *
+ *\li An idle timeout will not be generated until at least Now + the
+ * timer's interval if 'timer' is a once timer with a non-zero
+ * interval.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li No memory
+ *\li Unexpected error
+ */
+
+isc_result_t
+isc_timer_touch(isc_timer_t *timer);
+/*%<
+ * Set the last-touched time of 'timer' to the current time.
+ *
+ * Requires:
+ *
+ *\li 'timer' is a valid once timer.
+ *
+ * Ensures:
+ *
+ *\li An idle timeout will not be generated until at least Now + the
+ * timer's interval if 'timer' is a once timer with a non-zero
+ * interval.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li Unexpected error
+ */
+
+void
+isc_timer_destroy(isc_timer_t **timerp);
+/*%<
+ * Destroy *timerp.
+ *
+ * Requires:
+ *
+ *\li 'timerp' points to a valid timer.
+ *
+ * Ensures:
+ *
+ *\li *timerp is NULL.
+ *
+ *\code
+ * The timer will be shutdown
+ *
+ * The timer will detach from its task
+ *
+ * All resources used by the timer have been freed
+ *
+ * Any events already posted by the timer will be purged.
+ * Therefore, if isc_timer_destroy() is called in the context
+ * of the timer's task, it is guaranteed that no more
+ * timer event callbacks will run after the call.
+ *
+ * If this function is called from the timer event callback
+ * the event itself must be destroyed before the timer
+ * itself.
+ *\endcode
+ */
+
+isc_timertype_t
+isc_timer_gettype(isc_timer_t *timer);
+/*%<
+ * Return the timer type.
+ *
+ * Requires:
+ *
+ *\li 'timer' to be a valid timer.
+ */
+
+void
+isc_timermgr_poke(isc_timermgr_t *m);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/tls.h b/lib/isc/include/isc/tls.h
new file mode 100644
index 0000000..efffffb
--- /dev/null
+++ b/lib/isc/include/isc/tls.h
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/mem.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/types.h>
+
+typedef struct ssl_ctx_st isc_tlsctx_t;
+typedef struct ssl_st isc_tls_t;
+
+typedef struct x509_store_st isc_tls_cert_store_t;
+
+void
+isc_tlsctx_free(isc_tlsctx_t **ctpx);
+/*%<
+ * Free a TLS client or server context.
+ *
+ * Requires:
+ *\li 'ctxp' != NULL and '*ctxp' != NULL.
+ */
+
+void
+isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget);
+/*%<
+ * Attach to the TLS context.
+ *
+ * Requires:
+ *\li 'src' != NULL;
+ *\li 'ptarget' != NULL;
+ *\li '*ptarget' == NULL.
+ */
+
+isc_result_t
+isc_tlsctx_createserver(const char *keyfile, const char *certfile,
+ isc_tlsctx_t **ctxp);
+/*%<
+ * Set up a TLS server context, using the key and certificate specified in
+ * 'keyfile' and 'certfile', or a self-generated ephemeral key and
+ * certificdate if both 'keyfile' and 'certfile' are NULL.
+ *
+ * Requires:
+ *\li 'ctxp' != NULL and '*ctxp' == NULL.
+ *\li 'keyfile' and 'certfile' are either both NULL or both non-NULL.
+ */
+
+isc_result_t
+isc_tlsctx_createclient(isc_tlsctx_t **ctxp);
+/*%<
+ * Set up a TLS client context.
+ *
+ * Requires:
+ *\li 'ctxp' != NULL and '*ctxp' == NULL.
+ */
+
+isc_result_t
+isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile,
+ const char *certfile);
+/*%<
+ * Load a TLS certificate into a TLS context.
+ *
+ * Requires:
+ *\li 'ctx' != NULL;
+ *\li 'keyfile' and 'certfile' are both non-NULL.
+ */
+
+typedef enum isc_tls_protocol_version {
+ /* these must be the powers of two */
+ ISC_TLS_PROTO_VER_1_2 = 1 << 0,
+ ISC_TLS_PROTO_VER_1_3 = 1 << 1,
+ ISC_TLS_PROTO_VER_UNDEFINED,
+} isc_tls_protocol_version_t;
+
+void
+isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions);
+/*%<
+ * Sets the supported TLS protocol versions via the 'tls_versions' bit
+ * set argument (see `isc_tls_protocol_version_t` enum for the
+ * expected values).
+ *
+ * Requires:
+ *\li 'ctx' != NULL;
+ *\li 'tls_versions' != 0.
+ */
+
+bool
+isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver);
+/*%<
+ * Check in runtime that the specified TLS protocol versions is supported.
+ */
+
+isc_tls_protocol_version_t
+isc_tls_protocol_name_to_version(const char *name);
+/*%<
+ * Convert the protocol version string into the version of
+ * 'isc_tls_protocol_version_t' type.
+ * Requires:
+ *\li 'name' != NULL.
+ */
+
+bool
+isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file);
+/*%<
+ * Load Diffie-Hellman parameters file and apply it to the given TLS context
+ * 'ctx'.
+ *
+ * Requires:
+ * \li 'ctx' != NULL;
+ * \li 'dhaprams_file' a valid pointer to a non empty string.
+ */
+
+bool
+isc_tls_cipherlist_valid(const char *cipherlist);
+/*%<
+ * Check if cipher list string is valid.
+ *
+ * Requires:
+ * \li 'cipherlist' a valid pointer to a non empty string.
+ */
+
+void
+isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist);
+/*%<
+ * Set cipher list string for on the given TLS context 'ctx'.
+ *
+ * Requires:
+ * \li 'ctx' != NULL;
+ * \li 'cipherlist' a valid pointer to a non empty string.
+ */
+
+void
+isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer);
+/*%<
+ * Make the given TLS context 'ctx' to prefer or to not prefer
+ * server side ciphers during the ciphers negotiation.
+ *
+ * Requires:
+ * \li 'ctx' != NULL.
+ */
+
+void
+isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use);
+/*%<
+ * Enable/Disable stateless session resumptions tickets on the given
+ * TLS context 'ctx' (see RFC5077).
+ *
+ * Requires:
+ * \li 'ctx' != NULL.
+ */
+
+isc_tls_t *
+isc_tls_create(isc_tlsctx_t *ctx);
+/*%<
+ * Set up the structure to hold data for a new TLS connection.
+ *
+ * Requires:
+ *\li 'ctx' != NULL.
+ */
+
+void
+isc_tls_free(isc_tls_t **tlsp);
+/*%<
+ * Free a TLS structure.
+ *
+ * Requires:
+ *\li 'tlsp' != NULL and '*tlsp' != NULL.
+ */
+
+const char *
+isc_tls_verify_peer_result_string(isc_tls_t *tls);
+/*%<
+ * Return a user readable description of a remote peer's certificate
+ * validation.
+ *
+ * Requires:
+ *\li 'tls' != NULL.
+ */
+
+#if HAVE_LIBNGHTTP2
+void
+isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx);
+void
+isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *ctx);
+/*%<
+ * Enable HTTP/2 Application Layer Protocol Negotation for 'ctx'.
+ *
+ * Requires:
+ *\li 'ctx' is not NULL.
+ */
+#endif /* HAVE_LIBNGHTTP2 */
+
+void
+isc_tls_get_selected_alpn(isc_tls_t *tls, const unsigned char **alpn,
+ unsigned int *alpnlen);
+
+#define ISC_TLS_DOT_PROTO_ALPN_ID "dot"
+#define ISC_TLS_DOT_PROTO_ALPN_ID_LEN 3
+
+void
+isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx);
+void
+isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *ctx);
+/*%<
+ * Enable DoT Application Layer Protocol Negotation for 'ctx'.
+ *
+ * Requires:
+ *\li 'ctx' is not NULL.
+ */
+
+isc_result_t
+isc_tlsctx_enable_peer_verification(isc_tlsctx_t *ctx, const bool is_server,
+ isc_tls_cert_store_t *store,
+ const char *hostname,
+ bool hostname_ignore_subject);
+/*%<
+ * Enable peer certificate and, optionally, hostname (for client contexts)
+ * verification.
+ *
+ * Requires:
+ *\li 'ctx' is not NULL;
+ *\li 'store' is not NULL.
+ */
+
+isc_result_t
+isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file);
+/*%<
+ * Load the list of CA-certificate names from a CA-bundle file to
+ * send by the server to a client when requesting a peer certificate.
+ * Usually used in conjunction with
+ * isc_tlsctx_enable_peer_validation().
+ *
+ * Requires:
+ *\li 'ctx' is not NULL;
+ *\li 'ca_bundle_file' is not NULL.
+ */
+
+isc_result_t
+isc_tls_cert_store_create(const char *ca_bundle_filename,
+ isc_tls_cert_store_t **pstore);
+/*%<
+ * Create X509 certificate store. The 'ca_bundle_filename' might be
+ * 'NULL' or an empty string, which means use the default system wide
+ * bundle/directory.
+ *
+ * Requires:
+ *\li 'pstore' is a valid pointer to a pointer containing 'NULL'.
+ */
+
+void
+isc_tls_cert_store_free(isc_tls_cert_store_t **pstore);
+/*%<
+ * Free X509 certificate store.
+ *
+ * Requires:
+ *\li 'pstore' is a valid pointer to a pointer containing a non-'NULL' value.
+ */
+
+typedef struct isc_tlsctx_client_session_cache isc_tlsctx_client_session_cache_t;
+/*%<
+ * TLS client session cache is an object which allows efficient
+ * storing and retrieval of previously saved TLS sessions so that they
+ * can be resumed. This object is supposed to be a foundation for
+ * implementing TLS session resumption - a standard technique to
+ * reduce the cost of re-establishing a connection to the remote
+ * server endpoint.
+ *
+ * OpenSSL does server-side TLS session caching transparently by
+ * default. However, on the client-side, a TLS session to resume must
+ * be manually specified when establishing the TLS connection. The TLS
+ * client session cache is precisely the foundation for that.
+ *
+ * The cache has been designed to have the following characteristics:
+ *
+ * - Fixed maximal number of entries to not keep too many obsolete
+ * sessions within the cache;
+ *
+ * - The cache is indexed by character string keys. Each string is a
+ * key representing a remote endpoint (unique remote endpoint name,
+ * e.g. combination of the remote IP address and port);
+ *
+ * - Least Recently Used (LRU) cache replacement policy while allowing
+ * easy removal of obsolete entries;
+ *
+ * - Ability to store multiple TLS sessions associated with the given
+ * key (remote endpoint name). This characteristic is important if
+ * multiple connections to the same remote server can be established;
+ *
+ * - Ability to efficiently retrieve the most recent TLS sessions
+ * associated with the key (remote endpoint name).
+ *
+ * Because of these characteristics, the cache will end up having the
+ * necessary amount of resumable TLS session parameters to the most
+ * used remote endpoints ("hot entries") after a short period of
+ * initial use ("warmup").
+ *
+ * Attempting to resume TLS sessions is an optimisation, which is not
+ * guaranteed to succeed because it requires the same session to be
+ * present in the server session caches. If it is not the case, the
+ * usual handshake procedure takes place. However, when session
+ * resumption is successful, it reduces the amount of the
+ * computational resources required as well as the amount of data to
+ * be transmitted when (re)establishing the connection. Also, it
+ * reduces round trip time (by reducing the number of packets to
+ * transmit).
+ *
+ * This optimisation is important in the context of DNS because the
+ * amount of data within the full handshake messages might be
+ * comparable to or surpass the size of a typical DNS message.
+ */
+
+void
+isc_tlsctx_client_session_cache_create(
+ isc_mem_t *mctx, isc_tlsctx_t *ctx, const size_t max_entries,
+ isc_tlsctx_client_session_cache_t **cachep);
+/*%<
+ * Create a new TLS client session cache object.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context object;
+ *\li 'ctx' is a valid TLS context object;
+ *\li 'max_entries' is a positive number;
+ *\li 'cachep' is a valid pointer to a pointer which must be equal to NULL.
+ */
+
+void
+isc_tlsctx_client_session_cache_attach(
+ isc_tlsctx_client_session_cache_t *source,
+ isc_tlsctx_client_session_cache_t **targetp);
+/*%<
+ * Create a reference to the TLS client session cache object.
+ *
+ * Requires:
+ *\li 'source' is a valid TLS client session cache object;
+ *\li 'targetp' is a valid pointer to a pointer which must equal NULL.
+ */
+
+void
+isc_tlsctx_client_session_cache_detach(
+ isc_tlsctx_client_session_cache_t **cachep);
+/*%<
+ * Remove a reference to the TLS client session cache object.
+ *
+ * Requires:
+ *\li 'cachep' is a pointer to a pointer to a valid TLS client session cache
+ *object.
+ */
+
+void
+isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls);
+/*%<
+ * Add a resumable TLS client session within 'tls' to the TLS client
+ * session cache object 'cache' and associate it with
+ * 'remote_server_name' string. Also, the oldest entry from the cache
+ * might get removed if the cache is full.
+ *
+ * Requires:
+ *\li 'cache' is a pointer to a valid TLS client session cache object;
+ *\li 'remote_peer_name' is a pointer to a non empty character string;
+ *\li 'tls' is a valid, non-'NULL' pointer to a TLS connection state object.
+ */
+
+void
+isc_tlsctx_client_session_cache_keep_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls);
+/*%<
+ * The same as 'isc_tlsctx_client_session_cache_keep()', but uses a
+ * 'isc_sockaddr_t' as a key, instead of a character string.
+ *
+ * Requires:
+ *\li 'remote_peer' is a valid, non-'NULL', pointer to an 'isc_sockaddr_t'
+ *object.
+ */
+
+void
+isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_server_name, isc_tls_t *tls);
+/*%
+ * Try to restore a previously stored TLS session denoted by a remote
+ * server name as a key ('remote_server_name') into the given TLS
+ * connection state object ('tls'). The successfully restored session
+ * gets removed from the cache.
+ *
+ * Requires:
+ *\li 'cache' is a pointer to a valid TLS client session cache object;
+ *\li 'remote_peer_name' is a pointer to a non empty character string;
+ *\li 'tls' is a valid, non-'NULL' pointer to a TLS connection state object.
+ */
+
+void
+isc_tlsctx_client_session_cache_reuse_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls);
+/*%<
+ * The same as 'isc_tlsctx_client_session_cache_reuse()', but uses a
+ * 'isc_sockaddr_t' as a key, instead of a character string.
+ *
+ * Requires:
+ *\li 'remote_peer' is a valid, non-'NULL' pointer to an 'isc_sockaddr_t'
+ *object.
+ */
+
+const isc_tlsctx_t *
+isc_tlsctx_client_session_cache_getctx(isc_tlsctx_client_session_cache_t *cache);
+/*%<
+ * Returns a TLS context associated with the given TLS client
+ * session cache object. The function is intended to be used to
+ * implement the sanity checks ('INSIST()'s and 'REQUIRE()'s).
+ *
+ * Requires:
+ *\li 'cache' is a pointer to a valid TLS client session cache object.
+ */
+
+#define ISC_TLSCTX_CLIENT_SESSION_CACHE_DEFAULT_SIZE (150)
+/*%<
+ * The default maximum size of a TLS client session cache. The value
+ * should be large enough to hold enough sessions to successfully
+ * re-establish connections to the most remote TLS servers, but not
+ * too big to avoid keeping too much obsolete sessions.
+ */
+
+typedef struct isc_tlsctx_cache isc_tlsctx_cache_t;
+/*%<
+ * The TLS context cache is an object which allows retrieving a
+ * previously created TLS context based on the following tuple:
+ *
+ * 1. The name of a TLS entry, as defined in the configuration file;
+ * 2. A transport type. Currently, only TLS (DoT) and HTTPS (DoH) are
+ * supported;
+ * 3. An IP address family (AF_INET or AF_INET6).
+ *
+ * There are multiple uses for this object:
+ *
+ * First, it allows reuse of client-side contexts during zone transfers.
+ * That, in turn, allows use of session caches associated with these
+ * contexts, which enables TLS session resumption, making establishment
+ * of XoT connections faster and computationally cheaper.
+ *
+ * Second, it can be extended to be used as storage for TLS context related
+ * data, as defined in 'tls' statements in the configuration file (for
+ * example, CA-bundle intermediate certificate storage, client-side contexts
+ * with pre-loaded certificates in a case of Mutual TLS, etc). This will
+ * be used to implement Strict/Mutual TLS.
+ *
+ * Third, it avoids creating an excessive number of server-side TLS
+ * contexts, which might help to reduce the number of contexts
+ * created during server initialisation and reconfiguration.
+ */
+
+typedef enum {
+ isc_tlsctx_cache_none = 0,
+ isc_tlsctx_cache_tls,
+ isc_tlsctx_cache_https,
+ isc_tlsctx_cache_count
+} isc_tlsctx_cache_transport_t;
+/*%< TLS context cache transport type values. */
+
+void
+isc_tlsctx_cache_create(isc_mem_t *mctx, isc_tlsctx_cache_t **cachep);
+/*%<
+ * Create a new TLS context cache object.
+ *
+ * Requires:
+ *\li 'mctx' is a valid memory context;
+ *\li 'cachep' is a valid pointer to a pointer which must be equal to NULL.
+ */
+
+void
+isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source,
+ isc_tlsctx_cache_t **targetp);
+/*%<
+ * Create a reference to the TLS context cache object.
+ *
+ * Requires:
+ *\li 'source' is a valid TLS context cache object;
+ *\li 'targetp' is a valid pointer to a pointer which must equal NULL.
+ */
+
+void
+isc_tlsctx_cache_detach(isc_tlsctx_cache_t **cachep);
+/*%<
+ * Remove a reference to the TLS context cache object.
+ *
+ * Requires:
+ *\li 'cachep' is a pointer to a pointer to a valid TLS
+ * context cache object.
+ */
+
+isc_result_t
+isc_tlsctx_cache_add(
+ isc_tlsctx_cache_t *cache, const char *name,
+ const isc_tlsctx_cache_transport_t transport, const uint16_t family,
+ isc_tlsctx_t *ctx, isc_tls_cert_store_t *store,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ isc_tlsctx_t **pfound, isc_tls_cert_store_t **pfound_store,
+ isc_tlsctx_client_session_cache_t **pfound_client_sess_cache);
+/*%<
+ *
+ * Add a new TLS context and its associated data to the TLS context
+ * cache. 'pfound' is an optional pointer, which can be used to
+ * retrieve an already existing TLS context object in a case it
+ * exists.
+ *
+ * The passed certificates store object ('store') possession is
+ * transferred to the cache object in a case of success. In some cases
+ * it might be destroyed immediately upon the call completion.
+ *
+ * The possession of the passed TLS client session cache
+ * ('client_sess_cache') is also transferred to the cache object in a
+ * case of success.
+ *
+ * Requires:
+ *\li 'cache' is a valid pointer to a TLS context cache object;
+ *\li 'name' is a valid pointer to a non-empty string;
+ *\li 'transport' is a valid transport identifier (currently only
+ * TLS/DoT and HTTPS/DoH are supported);
+ *\li 'family' - either 'AF_INET' or 'AF_INET6';
+ *\li 'ctx' - a valid pointer to a valid TLS context object;
+ *\li 'store' - a valid pointer to a valid TLS certificates store object or
+ * 'NULL';
+ *\li 'client_sess_cache' - a valid pointer to a valid TLS client sessions
+ *cache object or 'NULL.
+ *
+ * Returns:
+ *\li #ISC_R_EXISTS - node of the same key already exists;
+ *\li #ISC_R_SUCCESS - the new entry has been added successfully.
+ */
+
+isc_result_t
+isc_tlsctx_cache_find(
+ isc_tlsctx_cache_t *cache, const char *name,
+ const isc_tlsctx_cache_transport_t transport, const uint16_t family,
+ isc_tlsctx_t **pctx, isc_tls_cert_store_t **pstore,
+ isc_tlsctx_client_session_cache_t **pfound_client_sess_cache);
+/*%<
+ * Look up a TLS context and its associated data in the TLS context cache.
+ *
+ * Requires:
+ *\li 'cache' is a valid pointer to a TLS context cache object;
+ *\li 'name' is a valid pointer to a non empty string;
+ *\li 'transport' - a valid transport identifier (currently only
+ * TLS/DoT and HTTPS/DoH are supported;
+ *\li 'family' - either 'AF_INET' or 'AF_INET6';
+ *\li 'pctx' - a valid pointer to a non-NULL pointer;
+ *\li 'pstore' - a valid pointer to a non-NULL pointer or 'NULL'.
+ *\li 'pfound_client_sess_cache' - a valid pointer to a non-NULL pointer or
+ *'NULL'.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS - the context has been found;
+ *\li #ISC_R_NOTFOUND - the context has not been found. In such a case,
+ * 'pstore' still might get initialised as there is one to many
+ * relation between stores and contexts.
+ */
+
+void
+isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx);
+/*%<
+ * Set context within which session can be reused to a randomly
+ * generated value. This one is used for TLS session resumption using
+ * session IDs. See OpenSSL documentation for
+ * 'SSL_CTX_set_session_id_context()'.
+ *
+ * It might be worth noting that usually session ID contexts are kept
+ * static for an application and particular certificate
+ * combination. However, for the cases when exporting server side TLS
+ * session cache to/loading from external memory is not required, we
+ * might use random IDs just fine. See,
+ * e.g. 'ngx_ssl_session_id_context()' in NGINX for an example of how
+ * a session ID might be obtained.
+ *
+ * Requires:
+ *\li 'ctx' - a valid non-NULL pointer;
+ */
diff --git a/lib/isc/include/isc/tm.h b/lib/isc/include/isc/tm.h
new file mode 100644
index 0000000..58feec4
--- /dev/null
+++ b/lib/isc/include/isc/tm.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
+
+/*! \file isc/tm.h
+ * Provides portable conversion routines for struct tm.
+ */
+#include <time.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+time_t
+isc_tm_timegm(struct tm *tm);
+/*
+ * Convert a tm structure to time_t, using UTC rather than the local
+ * time zone.
+ */
+
+char *
+isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm);
+/*
+ * Parse a formatted date string into struct tm.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/types.h b/lib/isc/include/isc/types.h
new file mode 100644
index 0000000..7e0fc02
--- /dev/null
+++ b/lib/isc/include/isc/types.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/result.h>
+
+/*! \file isc/types.h
+ * \brief
+ * OS-specific types, from the OS-specific include directories.
+ */
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/offset.h>
+
+/*
+ * XXXDCL This is just for ISC_LIST and ISC_LINK, but gets all of the other
+ * list macros too.
+ */
+#include <isc/list.h>
+
+/* Core Types. Alphabetized by defined type. */
+
+typedef struct isc_astack isc_astack_t; /*%< Array-based fast stack */
+typedef struct isc_appctx isc_appctx_t; /*%< Application context */
+typedef struct isc_buffer isc_buffer_t; /*%< Buffer */
+typedef ISC_LIST(isc_buffer_t) isc_bufferlist_t; /*%< Buffer List */
+typedef struct isc_constregion isc_constregion_t; /*%< Const region */
+typedef struct isc_consttextregion isc_consttextregion_t; /*%< Const Text Region
+ */
+typedef struct isc_counter isc_counter_t; /*%< Counter */
+typedef struct isc_event isc_event_t; /*%< Event */
+typedef ISC_LIST(isc_event_t) isc_eventlist_t; /*%< Event List */
+typedef unsigned int isc_eventtype_t; /*%< Event Type */
+typedef struct isc_hash isc_hash_t; /*%< Hash */
+typedef struct isc_httpd isc_httpd_t; /*%< HTTP client */
+typedef void(isc_httpdfree_t)(isc_buffer_t *, void *); /*%< HTTP free function
+ */
+typedef struct isc_httpdmgr isc_httpdmgr_t; /*%< HTTP manager */
+typedef struct isc_httpdurl isc_httpdurl_t; /*%< HTTP URL */
+typedef void(isc_httpdondestroy_t)(void *); /*%< Callback on destroying httpd */
+typedef struct isc_interface isc_interface_t; /*%< Interface */
+typedef struct isc_interfaceiter isc_interfaceiter_t; /*%< Interface Iterator */
+typedef struct isc_interval isc_interval_t; /*%< Interval */
+typedef struct isc_lex isc_lex_t; /*%< Lex */
+typedef struct isc_log isc_log_t; /*%< Log */
+typedef struct isc_logcategory isc_logcategory_t; /*%< Log Category */
+typedef struct isc_logconfig isc_logconfig_t; /*%< Log Configuration */
+typedef struct isc_logmodule isc_logmodule_t; /*%< Log Module */
+typedef struct isc_mem isc_mem_t; /*%< Memory */
+typedef struct isc_mempool isc_mempool_t; /*%< Memory Pool */
+typedef struct isc_netaddr isc_netaddr_t; /*%< Net Address */
+typedef struct isc_netprefix isc_netprefix_t; /*%< Net Prefix */
+typedef struct isc_nm isc_nm_t; /*%< Network manager */
+typedef struct isc_nmsocket isc_nmsocket_t; /*%< Network manager socket */
+typedef struct isc_nmhandle isc_nmhandle_t; /*%< Network manager handle */
+typedef struct isc_portset isc_portset_t; /*%< Port Set */
+typedef struct isc_quota isc_quota_t; /*%< Quota */
+typedef struct isc_ratelimiter isc_ratelimiter_t; /*%< Rate Limiter */
+typedef struct isc_region isc_region_t; /*%< Region */
+typedef uint64_t isc_resourcevalue_t; /*%< Resource Value */
+typedef struct isc_rwlock isc_rwlock_t; /*%< Read Write Lock */
+typedef struct isc_sockaddr isc_sockaddr_t; /*%< Socket Address */
+typedef ISC_LIST(isc_sockaddr_t) isc_sockaddrlist_t; /*%< Socket Address List
+ * */
+typedef struct isc_stats isc_stats_t; /*%< Statistics */
+typedef int_fast64_t isc_statscounter_t;
+typedef struct isc_symtab isc_symtab_t; /*%< Symbol Table */
+typedef struct isc_task isc_task_t; /*%< Task */
+typedef ISC_LIST(isc_task_t) isc_tasklist_t; /*%< Task List */
+typedef struct isc_taskmgr isc_taskmgr_t; /*%< Task Manager */
+typedef struct isc_textregion isc_textregion_t; /*%< Text Region */
+typedef struct isc_time isc_time_t; /*%< Time */
+typedef struct isc_timer isc_timer_t; /*%< Timer */
+typedef struct isc_timermgr isc_timermgr_t; /*%< Timer Manager */
+
+#if HAVE_LIBNGHTTP2
+typedef struct isc_nm_http_endpoints isc_nm_http_endpoints_t;
+/*%< HTTP endpoints set */
+#endif /* HAVE_LIBNGHTTP2 */
+
+typedef void (*isc_taskaction_t)(isc_task_t *, isc_event_t *);
+
+/*% Resource */
+typedef enum {
+ isc_resource_coresize = 1,
+ isc_resource_cputime,
+ isc_resource_datasize,
+ isc_resource_filesize,
+ isc_resource_lockedmemory,
+ isc_resource_openfiles,
+ isc_resource_processes,
+ isc_resource_residentsize,
+ isc_resource_stacksize
+} isc_resource_t;
+
+/*% Statistics formats (text file or XML) */
+typedef enum {
+ isc_statsformat_file,
+ isc_statsformat_xml,
+ isc_statsformat_json
+} isc_statsformat_t;
+
+typedef enum isc_nmsocket_type {
+ isc_nm_nonesocket = 0,
+ isc_nm_udpsocket = 1 << 1,
+ isc_nm_tcpsocket = 1 << 2,
+ isc_nm_tcpdnssocket = 1 << 3,
+ isc_nm_tlssocket = 1 << 4,
+ isc_nm_tlsdnssocket = 1 << 5,
+ isc_nm_httpsocket = 1 << 6,
+ isc_nm_maxsocket,
+
+ isc_nm_udplistener, /* Aggregate of nm_udpsocks */
+ isc_nm_tcplistener,
+ isc_nm_tlslistener,
+ isc_nm_tcpdnslistener,
+ isc_nm_tlsdnslistener,
+ isc_nm_httplistener
+} isc_nmsocket_type;
+
+typedef isc_nmsocket_type isc_nmsocket_type_t;
diff --git a/lib/isc/include/isc/url.h b/lib/isc/include/isc/url.h
new file mode 100644
index 0000000..d3b935b
--- /dev/null
+++ b/lib/isc/include/isc/url.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 and MIT
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <isc/result.h>
+
+/*
+ * Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
+ * faster
+ */
+#ifndef HTTP_PARSER_STRICT
+#define HTTP_PARSER_STRICT 1
+#endif
+
+typedef enum {
+ ISC_UF_SCHEMA = 0,
+ ISC_UF_HOST = 1,
+ ISC_UF_PORT = 2,
+ ISC_UF_PATH = 3,
+ ISC_UF_QUERY = 4,
+ ISC_UF_FRAGMENT = 5,
+ ISC_UF_USERINFO = 6,
+ ISC_UF_MAX = 7
+} isc_url_field_t;
+
+/* Result structure for isc_url_parse().
+ *
+ * Callers should index into field_data[] with UF_* values iff field_set
+ * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
+ * because we probably have padding left over), we convert any port to
+ * a uint16_t.
+ */
+typedef struct {
+ uint16_t field_set; /* Bitmask of (1 << UF_*) values */
+ uint16_t port; /* Converted UF_PORT string */
+
+ struct {
+ uint16_t off; /* Offset into buffer in which field starts */
+ uint16_t len; /* Length of run in buffer */
+ } field_data[ISC_UF_MAX];
+} isc_url_parser_t;
+
+isc_result_t
+isc_url_parse(const char *buf, size_t buflen, bool is_connect,
+ isc_url_parser_t *up);
+/*%<
+ * Parse a URL; return nonzero on failure
+ */
diff --git a/lib/isc/include/isc/utf8.h b/lib/isc/include/isc/utf8.h
new file mode 100644
index 0000000..5643c5d
--- /dev/null
+++ b/lib/isc/include/isc/utf8.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.
+ */
+
+/*! \file isc/utf8.h */
+
+#pragma once
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+bool
+isc_utf8_bom(const unsigned char *buf, size_t len);
+/*<
+ * Returns 'true' if the string of bytes in 'buf' starts
+ * with an UTF-8 Byte Order Mark.
+ *
+ * Requires:
+ *\li 'buf' != NULL
+ */
+
+bool
+isc_utf8_valid(const unsigned char *buf, size_t len);
+/*<
+ * Returns 'true' if the string of bytes in 'buf' is a valid UTF-8
+ * byte sequence otherwise 'false' is returned.
+ *
+ * Requires:
+ *\li 'buf' != NULL
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isc/include/isc/util.h b/lib/isc/include/isc/util.h
new file mode 100644
index 0000000..922661b
--- /dev/null
+++ b/lib/isc/include/isc/util.h
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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>
+
+/*! \file isc/util.h
+ * NOTE:
+ *
+ * This file is not to be included from any <isc/???.h> (or other) library
+ * files.
+ *
+ * \brief
+ * Including this file puts several macros in your name space that are
+ * not protected (as all the other ISC functions/macros do) by prepending
+ * ISC_ or isc_ to the name.
+ */
+
+/***
+ *** Clang Compatibility Macros
+ ***/
+
+#if !defined(__has_attribute)
+#define __has_attribute(x) 0
+#endif /* if !defined(__has_attribute) */
+
+#if !defined(__has_c_attribute)
+#define __has_c_attribute(x) 0
+#endif /* if !defined(__has_c_attribute) */
+
+#if !defined(__has_feature)
+#define __has_feature(x) 0
+#endif /* if !defined(__has_feature) */
+
+/***
+ *** General Macros.
+ ***/
+
+/*%
+ * Use this to hide unused function arguments.
+ * \code
+ * int
+ * foo(char *bar)
+ * {
+ * UNUSED(bar);
+ * }
+ * \endcode
+ */
+#define UNUSED(x) (void)(x)
+
+#if __GNUC__ >= 8 && !defined(__clang__)
+#define ISC_NONSTRING __attribute__((nonstring))
+#else /* if __GNUC__ >= 8 && !defined(__clang__) */
+#define ISC_NONSTRING
+#endif /* __GNUC__ */
+
+#if __has_c_attribute(fallthrough)
+#define FALLTHROUGH [[fallthrough]]
+#elif __GNUC__ >= 7 && !defined(__clang__)
+#define FALLTHROUGH __attribute__((fallthrough))
+#else
+/* clang-format off */
+#define FALLTHROUGH do {} while (0) /* FALLTHROUGH */
+/* clang-format on */
+#endif
+
+#if HAVE_FUNC_ATTRIBUTE_CONSTRUCTOR && HAVE_FUNC_ATTRIBUTE_DESTRUCTOR
+#define ISC_CONSTRUCTOR __attribute__((constructor))
+#define ISC_DESTRUCTOR __attribute__((destructor))
+#else
+#define ISC_CONSTRUCTOR
+#define ISC_DESTRUCTOR
+#endif
+
+/*%
+ * The opposite: silent warnings about stored values which are never read.
+ */
+#define POST(x) (void)(x)
+
+#define ISC_MAX(a, b) ((a) > (b) ? (a) : (b))
+#define ISC_MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define ISC_CLAMP(v, x, y) ((v) < (x) ? (x) : ((v) > (y) ? (y) : (v)))
+
+/*%
+ * Use this to remove the const qualifier of a variable to assign it to
+ * a non-const variable or pass it as a non-const function argument ...
+ * but only when you are sure it won't then be changed!
+ * This is necessary to sometimes shut up some compilers
+ * (as with gcc -Wcast-qual) when there is just no other good way to avoid the
+ * situation.
+ */
+#define DE_CONST(konst, var) \
+ do { \
+ union { \
+ const void *k; \
+ void *v; \
+ } _u; \
+ _u.k = konst; \
+ var = _u.v; \
+ } while (0)
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+/*%
+ * Use this in translation units that would otherwise be empty, to
+ * suppress compiler warnings.
+ */
+#define EMPTY_TRANSLATION_UNIT extern int isc__empty;
+
+/*%
+ * We use macros instead of calling the routines directly because
+ * the capital letters make the locking stand out.
+ * We RUNTIME_CHECK for success since in general there's no way
+ * for us to continue if they fail.
+ */
+
+#ifdef ISC_UTIL_TRACEON
+#define ISC_UTIL_TRACE(a) a
+#include <stdio.h> /* Required for fprintf/stderr when tracing. */
+#else /* ifdef ISC_UTIL_TRACEON */
+#define ISC_UTIL_TRACE(a)
+#endif /* ifdef ISC_UTIL_TRACEON */
+
+#include <isc/result.h> /* Contractual promise. */
+
+#define LOCK(lp) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "LOCKING %p %s %d\n", (lp), \
+ __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_mutex_lock((lp)) == ISC_R_SUCCESS); \
+ ISC_UTIL_TRACE(fprintf(stderr, "LOCKED %p %s %d\n", (lp), \
+ __FILE__, __LINE__)); \
+ } while (0)
+#define UNLOCK(lp) \
+ do { \
+ RUNTIME_CHECK(isc_mutex_unlock((lp)) == ISC_R_SUCCESS); \
+ ISC_UTIL_TRACE(fprintf(stderr, "UNLOCKED %p %s %d\n", (lp), \
+ __FILE__, __LINE__)); \
+ } while (0)
+
+#define BROADCAST(cvp) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "BROADCAST %p %s %d\n", (cvp), \
+ __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_condition_broadcast((cvp)) == \
+ ISC_R_SUCCESS); \
+ } while (0)
+#define SIGNAL(cvp) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "SIGNAL %p %s %d\n", (cvp), \
+ __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_condition_signal((cvp)) == ISC_R_SUCCESS); \
+ } while (0)
+#define WAIT(cvp, lp) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "WAIT %p LOCK %p %s %d\n", \
+ (cvp), (lp), __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_condition_wait((cvp), (lp)) == \
+ ISC_R_SUCCESS); \
+ ISC_UTIL_TRACE(fprintf(stderr, "WAITED %p LOCKED %p %s %d\n", \
+ (cvp), (lp), __FILE__, __LINE__)); \
+ } while (0)
+
+/*
+ * isc_condition_waituntil can return ISC_R_TIMEDOUT, so we
+ * don't RUNTIME_CHECK the result.
+ *
+ * XXX Also, can't really debug this then...
+ */
+
+#define WAITUNTIL(cvp, lp, tp) isc_condition_waituntil((cvp), (lp), (tp))
+
+#define RWLOCK(lp, t) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "RWLOCK %p, %d %s %d\n", (lp), \
+ (t), __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_rwlock_lock((lp), (t)) == ISC_R_SUCCESS); \
+ ISC_UTIL_TRACE(fprintf(stderr, "RWLOCKED %p, %d %s %d\n", \
+ (lp), (t), __FILE__, __LINE__)); \
+ } while (0)
+#define RWUNLOCK(lp, t) \
+ do { \
+ ISC_UTIL_TRACE(fprintf(stderr, "RWUNLOCK %p, %d %s %d\n", \
+ (lp), (t), __FILE__, __LINE__)); \
+ RUNTIME_CHECK(isc_rwlock_unlock((lp), (t)) == ISC_R_SUCCESS); \
+ } while (0)
+
+/*
+ * List Macros.
+ */
+#include <isc/list.h> /* Contractual promise. */
+
+#define LIST(type) ISC_LIST(type)
+#define INIT_LIST(type) ISC_LIST_INIT(type)
+#define LINK(type) ISC_LINK(type)
+#define INIT_LINK(elt, link) ISC_LINK_INIT(elt, link)
+#define HEAD(list) ISC_LIST_HEAD(list)
+#define TAIL(list) ISC_LIST_TAIL(list)
+#define EMPTY(list) ISC_LIST_EMPTY(list)
+#define PREV(elt, link) ISC_LIST_PREV(elt, link)
+#define NEXT(elt, link) ISC_LIST_NEXT(elt, link)
+#define APPEND(list, elt, link) ISC_LIST_APPEND(list, elt, link)
+#define PREPEND(list, elt, link) ISC_LIST_PREPEND(list, elt, link)
+#define UNLINK(list, elt, link) ISC_LIST_UNLINK(list, elt, link)
+#define ENQUEUE(list, elt, link) ISC_LIST_APPEND(list, elt, link)
+#define DEQUEUE(list, elt, link) ISC_LIST_UNLINK(list, elt, link)
+#define INSERTBEFORE(li, b, e, ln) ISC_LIST_INSERTBEFORE(li, b, e, ln)
+#define INSERTAFTER(li, a, e, ln) ISC_LIST_INSERTAFTER(li, a, e, ln)
+#define APPENDLIST(list1, list2, link) ISC_LIST_APPENDLIST(list1, list2, link)
+
+/*%
+ * Performance
+ */
+
+/* GCC defines __SANITIZE_ADDRESS__, so reuse the macro for clang */
+#if __has_feature(address_sanitizer)
+#define __SANITIZE_ADDRESS__ 1
+#endif /* if __has_feature(address_sanitizer) */
+
+#if __has_feature(thread_sanitizer)
+#define __SANITIZE_THREAD__ 1
+#endif /* if __has_feature(thread_sanitizer) */
+
+#if __SANITIZE_THREAD__
+#define ISC_NO_SANITIZE_THREAD __attribute__((no_sanitize("thread")))
+#else /* if __SANITIZE_THREAD__ */
+#define ISC_NO_SANITIZE_THREAD
+#endif /* if __SANITIZE_THREAD__ */
+
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6)
+#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
+#elif __has_feature(c_static_assert)
+#define STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
+#else /* if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6) */
+
+/* Courtesy of Joseph Quinsey: https://godbolt.org/z/K9RvWS */
+#define TOKENPASTE(a, b) a##b /* "##" is the "Token Pasting Operator" */
+#define EXPAND_THEN_PASTE(a, b) TOKENPASTE(a, b) /* expand then paste */
+#define STATIC_ASSERT(x, msg) \
+ enum { EXPAND_THEN_PASTE(ASSERT_line_, __LINE__) = 1 / ((msg) && (x)) }
+#endif /* if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR >= 6) */
+
+#ifdef UNIT_TESTING
+extern void
+mock_assert(const int result, const char *const expression,
+ const char *const file, const int line);
+/*
+ * Allow clang to determine that the following code is not reached
+ * by calling abort() if the condition fails. The abort() will
+ * never be executed as mock_assert() and _assert_true() longjmp
+ * or exit if the condition is false.
+ */
+#define REQUIRE(expression) \
+ ((!(expression)) \
+ ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \
+ : (void)0)
+#define ENSURE(expression) \
+ ((!(int)(expression)) \
+ ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \
+ : (void)0)
+#define INSIST(expression) \
+ ((!(expression)) \
+ ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \
+ : (void)0)
+#define INVARIANT(expression) \
+ ((!(expression)) \
+ ? (mock_assert(0, #expression, __FILE__, __LINE__), abort()) \
+ : (void)0)
+#define UNREACHABLE() \
+ (mock_assert(0, "unreachable", __FILE__, __LINE__), abort())
+#define _assert_true(c, e, f, l) \
+ ((c) ? (void)0 : (_assert_true(0, e, f, l), abort()))
+#define _assert_int_equal(a, b, f, l) \
+ (((a) == (b)) ? (void)0 : (_assert_int_equal(a, b, f, l), abort()))
+#define _assert_int_not_equal(a, b, f, l) \
+ (((a) != (b)) ? (void)0 : (_assert_int_not_equal(a, b, f, l), abort()))
+#else /* UNIT_TESTING */
+
+/*
+ * Assertions
+ */
+#include <isc/assertions.h> /* Contractual promise. */
+
+/*% Require Assertion */
+#define REQUIRE(e) ISC_REQUIRE(e)
+/*% Ensure Assertion */
+#define ENSURE(e) ISC_ENSURE(e)
+/*% Insist Assertion */
+#define INSIST(e) ISC_INSIST(e)
+/*% Invariant Assertion */
+#define INVARIANT(e) ISC_INVARIANT(e)
+
+#define UNREACHABLE() ISC_UNREACHABLE()
+
+#endif /* UNIT_TESTING */
+
+/*
+ * Errors
+ */
+#include <isc/error.h> /* Contractual promise. */
+#include <isc/strerr.h> /* for ISC_STRERRORSIZE */
+
+#define UNEXPECTED_ERROR(...) \
+ isc_error_unexpected(__FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define FATAL_ERROR(...) \
+ isc_error_fatal(__FILE__, __LINE__, __func__, __VA_ARGS__)
+
+#define REPORT_SYSERROR(report, err, fmt, ...) \
+ { \
+ char strerr[ISC_STRERRORSIZE]; \
+ strerror_r(err, strerr, sizeof(strerr)); \
+ report(__FILE__, __LINE__, __func__, fmt ": %s (%d)", \
+ ##__VA_ARGS__, strerr, err); \
+ }
+
+#define UNEXPECTED_SYSERROR(err, ...) \
+ REPORT_SYSERROR(isc_error_unexpected, err, __VA_ARGS__)
+
+#define FATAL_SYSERROR(err, ...) \
+ REPORT_SYSERROR(isc_error_fatal, err, __VA_ARGS__)
+
+#ifdef UNIT_TESTING
+
+#define RUNTIME_CHECK(cond) \
+ ((cond) ? (void)0 \
+ : (mock_assert(0, #cond, __FILE__, __LINE__), abort()))
+
+#else /* UNIT_TESTING */
+
+#define RUNTIME_CHECK(cond) \
+ ((cond) ? (void)0 : FATAL_ERROR("RUNTIME_CHECK(%s) failed", #cond))
+
+#endif /* UNIT_TESTING */
+
+/*%
+ * Time
+ */
+#define TIME_NOW(tp) RUNTIME_CHECK(isc_time_now((tp)) == ISC_R_SUCCESS)
+#define TIME_NOW_HIRES(tp) \
+ RUNTIME_CHECK(isc_time_now_hires((tp)) == ISC_R_SUCCESS)
+
+/*%
+ * Alignment
+ */
+#ifdef __GNUC__
+#define ISC_ALIGN(x, a) (((x) + (a)-1) & ~((typeof(x))(a)-1))
+#else /* ifdef __GNUC__ */
+#define ISC_ALIGN(x, a) (((x) + (a)-1) & ~((uintmax_t)(a)-1))
+#endif /* ifdef __GNUC__ */
+
+/*%
+ * Misc
+ */
+#include <isc/deprecated.h>
+
+/*%
+ * Swap
+ */
+#define ISC_SWAP(a, b) \
+ { \
+ typeof(a) __tmp_swap = a; \
+ a = b; \
+ b = __tmp_swap; \
+ }
diff --git a/lib/isc/interfaceiter.c b/lib/isc/interfaceiter.c
new file mode 100644
index 0000000..566c5bd
--- /dev/null
+++ b/lib/isc/interfaceiter.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <sys/ioctl.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_SOCKIO_H
+#include <sys/sockio.h> /* Required for ifiter_ioctl.c. */
+#endif /* ifdef HAVE_SYS_SOCKIO_H */
+
+#include <errno.h>
+#include <ifaddrs.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/interfaceiter.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+/* Must follow <isc/net.h>. */
+#ifdef HAVE_NET_IF6_H
+#include <net/if6.h>
+#endif /* ifdef HAVE_NET_IF6_H */
+#include <net/if.h>
+
+/* Common utility functions */
+
+/*%
+ * Extract the network address part from a "struct sockaddr".
+ * \brief
+ * The address family is given explicitly
+ * instead of using src->sa_family, because the latter does not work
+ * for copying a network mask obtained by SIOCGIFNETMASK (it does
+ * not have a valid address family).
+ */
+
+static void
+get_addr(unsigned int family, isc_netaddr_t *dst, struct sockaddr *src,
+ char *ifname) {
+ struct sockaddr_in6 *sa6;
+
+#if !defined(HAVE_IF_NAMETOINDEX)
+ UNUSED(ifname);
+#endif /* if !defined(HAVE_IF_NAMETOINDEX) */
+
+ /* clear any remaining value for safety */
+ memset(dst, 0, sizeof(*dst));
+
+ dst->family = family;
+ switch (family) {
+ case AF_INET:
+ memmove(&dst->type.in, &((struct sockaddr_in *)src)->sin_addr,
+ sizeof(struct in_addr));
+ break;
+ case AF_INET6:
+ sa6 = (struct sockaddr_in6 *)src;
+ memmove(&dst->type.in6, &sa6->sin6_addr,
+ sizeof(struct in6_addr));
+ if (sa6->sin6_scope_id != 0) {
+ isc_netaddr_setzone(dst, sa6->sin6_scope_id);
+ } else {
+ /*
+ * BSD variants embed scope zone IDs in the 128bit
+ * address as a kernel internal form. Unfortunately,
+ * the embedded IDs are not hidden from applications
+ * when getting access to them by sysctl or ioctl.
+ * We convert the internal format to the pure address
+ * part and the zone ID part.
+ * Since multicast addresses should not appear here
+ * and they cannot be distinguished from netmasks,
+ * we only consider unicast link-local addresses.
+ */
+ if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) {
+ uint16_t zone16;
+
+ memmove(&zone16, &sa6->sin6_addr.s6_addr[2],
+ sizeof(zone16));
+ zone16 = ntohs(zone16);
+ if (zone16 != 0) {
+ /* the zone ID is embedded */
+ isc_netaddr_setzone(dst,
+ (uint32_t)zone16);
+ dst->type.in6.s6_addr[2] = 0;
+ dst->type.in6.s6_addr[3] = 0;
+#ifdef HAVE_IF_NAMETOINDEX
+ } else if (ifname != NULL) {
+ unsigned int zone;
+
+ /*
+ * sin6_scope_id is still not provided,
+ * but the corresponding interface name
+ * is know. Use the interface ID as
+ * the link ID.
+ */
+ zone = if_nametoindex(ifname);
+ if (zone != 0) {
+ isc_netaddr_setzone(
+ dst, (uint32_t)zone);
+ }
+#endif /* ifdef HAVE_IF_NAMETOINDEX */
+ }
+ }
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * Include system-dependent code.
+ */
+
+#ifdef __linux
+#define ISC_IF_INET6_SZ \
+ sizeof("00000000000000000000000000000001 01 80 10 80 " \
+ "XXXXXXloXXXXXXXX\n")
+static isc_result_t
+linux_if_inet6_next(isc_interfaceiter_t *);
+static isc_result_t
+linux_if_inet6_current(isc_interfaceiter_t *);
+static void
+linux_if_inet6_first(isc_interfaceiter_t *iter);
+#endif /* ifdef __linux */
+
+/*% Iterator Magic */
+#define IFITER_MAGIC ISC_MAGIC('I', 'F', 'I', 'G')
+/*% Valid Iterator */
+#define VALID_IFITER(t) ISC_MAGIC_VALID(t, IFITER_MAGIC)
+
+#ifdef __linux
+static bool seenv6 = false;
+#endif /* ifdef __linux */
+
+/*% Iterator structure */
+struct isc_interfaceiter {
+ unsigned int magic; /*%< Magic number. */
+ isc_mem_t *mctx;
+ void *buf; /*%< (unused) */
+ unsigned int bufsize; /*%< (always 0) */
+ struct ifaddrs *ifaddrs; /*%< List of ifaddrs */
+ struct ifaddrs *pos; /*%< Ptr to current ifaddr */
+ isc_interface_t current; /*%< Current interface data. */
+ isc_result_t result; /*%< Last result code. */
+#ifdef __linux
+ FILE *proc;
+ char entry[ISC_IF_INET6_SZ];
+ isc_result_t valid;
+#endif /* ifdef __linux */
+};
+
+isc_result_t
+isc_interfaceiter_create(isc_mem_t *mctx, isc_interfaceiter_t **iterp) {
+ isc_interfaceiter_t *iter;
+ isc_result_t result;
+ char strbuf[ISC_STRERRORSIZE];
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(iterp != NULL);
+ REQUIRE(*iterp == NULL);
+
+ iter = isc_mem_get(mctx, sizeof(*iter));
+
+ iter->mctx = mctx;
+ iter->buf = NULL;
+ iter->bufsize = 0;
+ iter->ifaddrs = NULL;
+#ifdef __linux
+ /*
+ * Only open "/proc/net/if_inet6" if we have never seen a IPv6
+ * address returned by getifaddrs().
+ */
+ if (!seenv6) {
+ iter->proc = fopen("/proc/net/if_inet6", "r");
+ } else {
+ iter->proc = NULL;
+ }
+ iter->valid = ISC_R_FAILURE;
+#endif /* ifdef __linux */
+
+ if (getifaddrs(&iter->ifaddrs) < 0) {
+ strerror_r(errno, strbuf, sizeof(strbuf));
+ UNEXPECTED_ERROR("getting interface addresses: getifaddrs: %s",
+ strbuf);
+ result = ISC_R_UNEXPECTED;
+ goto failure;
+ }
+
+ /*
+ * A newly created iterator has an undefined position
+ * until isc_interfaceiter_first() is called.
+ */
+ iter->pos = NULL;
+ iter->result = ISC_R_FAILURE;
+
+ iter->magic = IFITER_MAGIC;
+ *iterp = iter;
+ return (ISC_R_SUCCESS);
+
+failure:
+#ifdef __linux
+ if (iter->proc != NULL) {
+ fclose(iter->proc);
+ }
+#endif /* ifdef __linux */
+ if (iter->ifaddrs != NULL) { /* just in case */
+ freeifaddrs(iter->ifaddrs);
+ }
+ isc_mem_put(mctx, iter, sizeof(*iter));
+ return (result);
+}
+
+/*
+ * Get information about the current interface to iter->current.
+ * If successful, return ISC_R_SUCCESS.
+ * If the interface has an unsupported address family,
+ * return ISC_R_IGNORE.
+ */
+
+static isc_result_t
+internal_current(isc_interfaceiter_t *iter) {
+ struct ifaddrs *ifa;
+ int family;
+ unsigned int namelen;
+
+ REQUIRE(VALID_IFITER(iter));
+
+ ifa = iter->pos;
+
+#ifdef __linux
+ if (iter->pos == NULL) {
+ return (linux_if_inet6_current(iter));
+ }
+#endif /* ifdef __linux */
+
+ INSIST(ifa != NULL);
+ INSIST(ifa->ifa_name != NULL);
+
+ if (ifa->ifa_addr == NULL) {
+ return (ISC_R_IGNORE);
+ }
+
+ family = ifa->ifa_addr->sa_family;
+ if (family != AF_INET && family != AF_INET6) {
+ return (ISC_R_IGNORE);
+ }
+
+#ifdef __linux
+ if (family == AF_INET6) {
+ seenv6 = true;
+ }
+#endif /* ifdef __linux */
+
+ memset(&iter->current, 0, sizeof(iter->current));
+
+ namelen = strlen(ifa->ifa_name);
+ if (namelen > sizeof(iter->current.name) - 1) {
+ namelen = sizeof(iter->current.name) - 1;
+ }
+
+ memset(iter->current.name, 0, sizeof(iter->current.name));
+ memmove(iter->current.name, ifa->ifa_name, namelen);
+
+ iter->current.flags = 0;
+
+ if ((ifa->ifa_flags & IFF_UP) != 0) {
+ iter->current.flags |= INTERFACE_F_UP;
+ }
+
+ if ((ifa->ifa_flags & IFF_POINTOPOINT) != 0) {
+ iter->current.flags |= INTERFACE_F_POINTTOPOINT;
+ }
+
+ if ((ifa->ifa_flags & IFF_LOOPBACK) != 0) {
+ iter->current.flags |= INTERFACE_F_LOOPBACK;
+ }
+
+ iter->current.af = family;
+
+ get_addr(family, &iter->current.address, ifa->ifa_addr, ifa->ifa_name);
+
+ if (ifa->ifa_netmask != NULL) {
+ get_addr(family, &iter->current.netmask, ifa->ifa_netmask,
+ ifa->ifa_name);
+ }
+
+ if (ifa->ifa_dstaddr != NULL &&
+ (iter->current.flags & INTERFACE_F_POINTTOPOINT) != 0)
+ {
+ get_addr(family, &iter->current.dstaddress, ifa->ifa_dstaddr,
+ ifa->ifa_name);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Step the iterator to the next interface. Unlike
+ * isc_interfaceiter_next(), this may leave the iterator
+ * positioned on an interface that will ultimately
+ * be ignored. Return ISC_R_NOMORE if there are no more
+ * interfaces, otherwise ISC_R_SUCCESS.
+ */
+static isc_result_t
+internal_next(isc_interfaceiter_t *iter) {
+ if (iter->pos != NULL) {
+ iter->pos = iter->pos->ifa_next;
+ }
+ if (iter->pos == NULL) {
+#ifdef __linux
+ if (!seenv6) {
+ return (linux_if_inet6_next(iter));
+ }
+#endif /* ifdef __linux */
+ return (ISC_R_NOMORE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+internal_destroy(isc_interfaceiter_t *iter) {
+#ifdef __linux
+ if (iter->proc != NULL) {
+ fclose(iter->proc);
+ }
+ iter->proc = NULL;
+#endif /* ifdef __linux */
+ if (iter->ifaddrs) {
+ freeifaddrs(iter->ifaddrs);
+ }
+ iter->ifaddrs = NULL;
+}
+
+static void
+internal_first(isc_interfaceiter_t *iter) {
+#ifdef __linux
+ linux_if_inet6_first(iter);
+#endif /* ifdef __linux */
+ iter->pos = iter->ifaddrs;
+}
+
+#ifdef __linux
+static void
+linux_if_inet6_first(isc_interfaceiter_t *iter) {
+ if (iter->proc != NULL) {
+ rewind(iter->proc);
+ (void)linux_if_inet6_next(iter);
+ } else {
+ iter->valid = ISC_R_NOMORE;
+ }
+}
+
+static isc_result_t
+linux_if_inet6_next(isc_interfaceiter_t *iter) {
+ if (iter->proc != NULL &&
+ fgets(iter->entry, sizeof(iter->entry), iter->proc) != NULL)
+ {
+ iter->valid = ISC_R_SUCCESS;
+ } else {
+ iter->valid = ISC_R_NOMORE;
+ }
+ return (iter->valid);
+}
+
+static isc_result_t
+linux_if_inet6_current(isc_interfaceiter_t *iter) {
+ char address[33];
+ char name[IF_NAMESIZE + 1];
+ struct in6_addr addr6;
+ unsigned int ifindex, prefix, flag3, flag4;
+ int res;
+ unsigned int i;
+
+ if (iter->valid != ISC_R_SUCCESS) {
+ return (iter->valid);
+ }
+ if (iter->proc == NULL) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR,
+ "/proc/net/if_inet6:iter->proc == NULL");
+ return (ISC_R_FAILURE);
+ }
+
+ res = sscanf(iter->entry, "%32[a-f0-9] %x %x %x %x %16s\n", address,
+ &ifindex, &prefix, &flag3, &flag4, name);
+ if (res != 6) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR,
+ "/proc/net/if_inet6:sscanf() -> %d (expected 6)",
+ res);
+ return (ISC_R_FAILURE);
+ }
+ if (strlen(address) != 32) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_INTERFACE, ISC_LOG_ERROR,
+ "/proc/net/if_inet6:strlen(%s) != 32", address);
+ return (ISC_R_FAILURE);
+ }
+ for (i = 0; i < 16; i++) {
+ unsigned char byte;
+ static const char hex[] = "0123456789abcdef";
+ byte = ((strchr(hex, address[i * 2]) - hex) << 4) |
+ (strchr(hex, address[i * 2 + 1]) - hex);
+ addr6.s6_addr[i] = byte;
+ }
+ iter->current.af = AF_INET6;
+ iter->current.flags = INTERFACE_F_UP;
+ isc_netaddr_fromin6(&iter->current.address, &addr6);
+ if (isc_netaddr_islinklocal(&iter->current.address)) {
+ isc_netaddr_setzone(&iter->current.address, (uint32_t)ifindex);
+ }
+ for (i = 0; i < 16; i++) {
+ if (prefix > 8) {
+ addr6.s6_addr[i] = 0xff;
+ prefix -= 8;
+ } else {
+ addr6.s6_addr[i] = (0xff << (8 - prefix)) & 0xff;
+ prefix = 0;
+ }
+ }
+ isc_netaddr_fromin6(&iter->current.netmask, &addr6);
+ strlcpy(iter->current.name, name, sizeof(iter->current.name));
+ return (ISC_R_SUCCESS);
+}
+#endif /* ifdef __linux */
+
+/*
+ * The remaining code is common to the sysctl and ioctl case.
+ */
+
+isc_result_t
+isc_interfaceiter_current(isc_interfaceiter_t *iter, isc_interface_t *ifdata) {
+ REQUIRE(iter->result == ISC_R_SUCCESS);
+ memmove(ifdata, &iter->current, sizeof(*ifdata));
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_interfaceiter_first(isc_interfaceiter_t *iter) {
+ isc_result_t result;
+
+ REQUIRE(VALID_IFITER(iter));
+
+ internal_first(iter);
+ for (;;) {
+ result = internal_current(iter);
+ if (result != ISC_R_IGNORE) {
+ break;
+ }
+ result = internal_next(iter);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ iter->result = result;
+ return (result);
+}
+
+isc_result_t
+isc_interfaceiter_next(isc_interfaceiter_t *iter) {
+ isc_result_t result;
+
+ REQUIRE(VALID_IFITER(iter));
+ REQUIRE(iter->result == ISC_R_SUCCESS);
+
+ for (;;) {
+ result = internal_next(iter);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ result = internal_current(iter);
+ if (result != ISC_R_IGNORE) {
+ break;
+ }
+ }
+ iter->result = result;
+ return (result);
+}
+
+void
+isc_interfaceiter_destroy(isc_interfaceiter_t **iterp) {
+ isc_interfaceiter_t *iter;
+ REQUIRE(iterp != NULL);
+ iter = *iterp;
+ *iterp = NULL;
+ REQUIRE(VALID_IFITER(iter));
+
+ internal_destroy(iter);
+ if (iter->buf != NULL) {
+ isc_mem_put(iter->mctx, iter->buf, iter->bufsize);
+ }
+
+ iter->magic = 0;
+ isc_mem_put(iter->mctx, iter, sizeof(*iter));
+}
diff --git a/lib/isc/iterated_hash.c b/lib/isc/iterated_hash.c
new file mode 100644
index 0000000..ad62381
--- /dev/null
+++ b/lib/isc/iterated_hash.c
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdio.h>
+
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+
+#include <isc/iterated_hash.h>
+#include <isc/util.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L || OPENSSL_API_LEVEL < 30000
+
+#include <openssl/sha.h>
+
+int
+isc_iterated_hash(unsigned char *out, const unsigned int hashalg,
+ const int iterations, const unsigned char *salt,
+ const int saltlength, const unsigned char *in,
+ const int inlength) {
+ REQUIRE(out != NULL);
+
+ int n = 0;
+ size_t len;
+ const unsigned char *buf;
+ SHA_CTX ctx;
+
+ if (hashalg != 1) {
+ return (0);
+ }
+
+ buf = in;
+ len = inlength;
+
+ do {
+ if (SHA1_Init(&ctx) != 1) {
+ ERR_clear_error();
+ return (0);
+ }
+
+ if (SHA1_Update(&ctx, buf, len) != 1) {
+ ERR_clear_error();
+ return (0);
+ }
+
+ if (SHA1_Update(&ctx, salt, saltlength) != 1) {
+ ERR_clear_error();
+ return (0);
+ }
+
+ if (SHA1_Final(out, &ctx) != 1) {
+ ERR_clear_error();
+ return (0);
+ }
+
+ buf = out;
+ len = SHA_DIGEST_LENGTH;
+ } while (n++ < iterations);
+
+ return (SHA_DIGEST_LENGTH);
+}
+
+#else
+
+#include <openssl/evp.h>
+
+#include <isc/md.h>
+
+int
+isc_iterated_hash(unsigned char *out, const unsigned int hashalg,
+ const int iterations, const unsigned char *salt,
+ const int saltlength, const unsigned char *in,
+ const int inlength) {
+ REQUIRE(out != NULL);
+
+ int n = 0;
+ size_t len;
+ unsigned int outlength = 0;
+ const unsigned char *buf;
+ EVP_MD_CTX *ctx;
+ ;
+ EVP_MD *md;
+
+ if (hashalg != 1) {
+ return (0);
+ }
+
+ ctx = EVP_MD_CTX_new();
+ RUNTIME_CHECK(ctx != NULL);
+ md = EVP_MD_fetch(NULL, "SHA1", NULL);
+ RUNTIME_CHECK(md != NULL);
+
+ buf = in;
+ len = inlength;
+
+ do {
+ if (EVP_DigestInit_ex(ctx, md, NULL) != 1) {
+ goto fail;
+ }
+
+ if (EVP_DigestUpdate(ctx, buf, len) != 1) {
+ goto fail;
+ }
+
+ if (EVP_DigestUpdate(ctx, salt, saltlength) != 1) {
+ goto fail;
+ }
+
+ if (EVP_DigestFinal_ex(ctx, out, &outlength) != 1) {
+ goto fail;
+ }
+
+ buf = out;
+ len = outlength;
+ } while (n++ < iterations);
+
+ EVP_MD_CTX_free(ctx);
+ EVP_MD_free(md);
+
+ return (outlength);
+
+fail:
+ EVP_MD_CTX_free(ctx);
+ EVP_MD_free(md);
+ ERR_clear_error();
+ return (0);
+}
+
+#endif
diff --git a/lib/isc/jemalloc_shim.h b/lib/isc/jemalloc_shim.h
new file mode 100644
index 0000000..493bf5f
--- /dev/null
+++ b/lib/isc/jemalloc_shim.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
+
+#if !defined(HAVE_JEMALLOC)
+
+#include <stddef.h>
+
+#include <isc/util.h>
+
+const char *malloc_conf = NULL;
+
+/* Without jemalloc, isc_mem_get_align() is equal to isc_mem_get() */
+#define MALLOCX_ALIGN(a) (a & 0) /* Clear the flag */
+#define MALLOCX_ZERO ((int)0x40)
+#define MALLOCX_TCACHE_NONE (0)
+#define MALLOCX_ARENA(a) (0)
+
+#if defined(HAVE_MALLOC_SIZE) || defined(HAVE_MALLOC_USABLE_SIZE)
+
+#include <stdlib.h>
+
+static inline void *
+mallocx(size_t size, int flags) {
+ UNUSED(flags);
+
+ return (malloc(size));
+}
+
+static inline void
+sdallocx(void *ptr, size_t size, int flags) {
+ UNUSED(size);
+ UNUSED(flags);
+
+ free(ptr);
+}
+
+static inline void *
+rallocx(void *ptr, size_t size, int flags) {
+ UNUSED(flags);
+ REQUIRE(size != 0);
+
+ return (realloc(ptr, size));
+}
+
+#ifdef HAVE_MALLOC_SIZE
+
+#include <malloc/malloc.h>
+
+static inline size_t
+sallocx(void *ptr, int flags) {
+ UNUSED(flags);
+
+ return (malloc_size(ptr));
+}
+
+#elif HAVE_MALLOC_USABLE_SIZE
+
+#ifdef __DragonFly__
+/*
+ * On DragonFly BSD 'man 3 malloc' advises us to include the following
+ * header to have access to malloc_usable_size().
+ */
+#include <malloc_np.h>
+#else
+#include <malloc.h>
+#endif
+
+static inline size_t
+sallocx(void *ptr, int flags) {
+ UNUSED(flags);
+
+ return (malloc_usable_size(ptr));
+}
+
+#endif /* HAVE_MALLOC_SIZE */
+
+#else /* defined(HAVE_MALLOC_SIZE) || defined (HAVE_MALLOC_USABLE_SIZE) */
+
+#include <stdlib.h>
+
+typedef union {
+ size_t size;
+ max_align_t __alignment;
+} size_info;
+
+static inline void *
+mallocx(size_t size, int flags) {
+ void *ptr = NULL;
+
+ UNUSED(flags);
+
+ size_info *si = malloc(size + sizeof(*si));
+ INSIST(si != NULL);
+
+ si->size = size;
+ ptr = &si[1];
+
+ return (ptr);
+}
+
+static inline void
+sdallocx(void *ptr, size_t size, int flags) {
+ size_info *si = &(((size_info *)ptr)[-1]);
+
+ UNUSED(size);
+ UNUSED(flags);
+
+ free(si);
+}
+
+static inline size_t
+sallocx(void *ptr, int flags) {
+ size_info *si = &(((size_info *)ptr)[-1]);
+
+ UNUSED(flags);
+
+ return (si[0].size);
+}
+
+static inline void *
+rallocx(void *ptr, size_t size, int flags) {
+ size_info *si = &(((size_info *)ptr)[-1]);
+
+ UNUSED(flags);
+
+ si = realloc(si, size + sizeof(*si));
+ INSIST(si != NULL);
+
+ si->size = size;
+ ptr = &si[1];
+
+ return (ptr);
+}
+
+#endif /* defined(HAVE_MALLOC_SIZE) || defined (HAVE_MALLOC_USABLE_SIZE) */
+
+#endif /* !defined(HAVE_JEMALLOC) */
diff --git a/lib/isc/lex.c b/lib/isc/lex.c
new file mode 100644
index 0000000..19ed4a7
--- /dev/null
+++ b/lib/isc/lex.c
@@ -0,0 +1,1136 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+
+#include <isc/buffer.h>
+#include <isc/file.h>
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/parseint.h>
+#include <isc/print.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+typedef struct inputsource {
+ isc_result_t result;
+ bool is_file;
+ bool need_close;
+ bool at_eof;
+ bool last_was_eol;
+ isc_buffer_t *pushback;
+ unsigned int ignored;
+ void *input;
+ char *name;
+ unsigned long line;
+ unsigned long saved_line;
+ ISC_LINK(struct inputsource) link;
+} inputsource;
+
+#define LEX_MAGIC ISC_MAGIC('L', 'e', 'x', '!')
+#define VALID_LEX(l) ISC_MAGIC_VALID(l, LEX_MAGIC)
+
+struct isc_lex {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ size_t max_token;
+ char *data;
+ unsigned int comments;
+ bool comment_ok;
+ bool last_was_eol;
+ unsigned int brace_count;
+ unsigned int paren_count;
+ unsigned int saved_paren_count;
+ isc_lexspecials_t specials;
+ LIST(struct inputsource) sources;
+};
+
+static isc_result_t
+grow_data(isc_lex_t *lex, size_t *remainingp, char **currp, char **prevp) {
+ char *tmp;
+
+ tmp = isc_mem_get(lex->mctx, lex->max_token * 2 + 1);
+ memmove(tmp, lex->data, lex->max_token + 1);
+ *currp = tmp + (*currp - lex->data);
+ if (*prevp != NULL) {
+ *prevp = tmp + (*prevp - lex->data);
+ }
+ isc_mem_put(lex->mctx, lex->data, lex->max_token + 1);
+ lex->data = tmp;
+ *remainingp += lex->max_token;
+ lex->max_token *= 2;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_lex_create(isc_mem_t *mctx, size_t max_token, isc_lex_t **lexp) {
+ isc_lex_t *lex;
+
+ /*
+ * Create a lexer.
+ */
+ REQUIRE(lexp != NULL && *lexp == NULL);
+
+ if (max_token == 0U) {
+ max_token = 1;
+ }
+
+ lex = isc_mem_get(mctx, sizeof(*lex));
+ lex->data = isc_mem_get(mctx, max_token + 1);
+ lex->mctx = mctx;
+ lex->max_token = max_token;
+ lex->comments = 0;
+ lex->comment_ok = true;
+ lex->last_was_eol = true;
+ lex->brace_count = 0;
+ lex->paren_count = 0;
+ lex->saved_paren_count = 0;
+ memset(lex->specials, 0, 256);
+ INIT_LIST(lex->sources);
+ lex->magic = LEX_MAGIC;
+
+ *lexp = lex;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_lex_destroy(isc_lex_t **lexp) {
+ isc_lex_t *lex;
+
+ /*
+ * Destroy the lexer.
+ */
+
+ REQUIRE(lexp != NULL);
+ lex = *lexp;
+ *lexp = NULL;
+ REQUIRE(VALID_LEX(lex));
+
+ while (!EMPTY(lex->sources)) {
+ RUNTIME_CHECK(isc_lex_close(lex) == ISC_R_SUCCESS);
+ }
+ if (lex->data != NULL) {
+ isc_mem_put(lex->mctx, lex->data, lex->max_token + 1);
+ }
+ lex->magic = 0;
+ isc_mem_put(lex->mctx, lex, sizeof(*lex));
+}
+
+unsigned int
+isc_lex_getcomments(isc_lex_t *lex) {
+ /*
+ * Return the current lexer commenting styles.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ return (lex->comments);
+}
+
+void
+isc_lex_setcomments(isc_lex_t *lex, unsigned int comments) {
+ /*
+ * Set allowed lexer commenting styles.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ lex->comments = comments;
+}
+
+void
+isc_lex_getspecials(isc_lex_t *lex, isc_lexspecials_t specials) {
+ /*
+ * Put the current list of specials into 'specials'.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ memmove(specials, lex->specials, 256);
+}
+
+void
+isc_lex_setspecials(isc_lex_t *lex, isc_lexspecials_t specials) {
+ /*
+ * The characters in 'specials' are returned as tokens. Along with
+ * whitespace, they delimit strings and numbers.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ memmove(lex->specials, specials, 256);
+}
+
+static isc_result_t
+new_source(isc_lex_t *lex, bool is_file, bool need_close, void *input,
+ const char *name) {
+ inputsource *source;
+
+ source = isc_mem_get(lex->mctx, sizeof(*source));
+ source->result = ISC_R_SUCCESS;
+ source->is_file = is_file;
+ source->need_close = need_close;
+ source->at_eof = false;
+ source->last_was_eol = lex->last_was_eol;
+ source->input = input;
+ source->name = isc_mem_strdup(lex->mctx, name);
+ source->pushback = NULL;
+ isc_buffer_allocate(lex->mctx, &source->pushback,
+ (unsigned int)lex->max_token);
+ source->ignored = 0;
+ source->line = 1;
+ ISC_LIST_INITANDPREPEND(lex->sources, source, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_lex_openfile(isc_lex_t *lex, const char *filename) {
+ isc_result_t result;
+ FILE *stream = NULL;
+
+ /*
+ * Open 'filename' and make it the current input source for 'lex'.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ result = isc_stdio_open(filename, "r", &stream);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = new_source(lex, true, true, stream, filename);
+ if (result != ISC_R_SUCCESS) {
+ (void)fclose(stream);
+ }
+ return (result);
+}
+
+isc_result_t
+isc_lex_openstream(isc_lex_t *lex, FILE *stream) {
+ char name[128];
+
+ /*
+ * Make 'stream' the current input source for 'lex'.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ snprintf(name, sizeof(name), "stream-%p", stream);
+
+ return (new_source(lex, true, false, stream, name));
+}
+
+isc_result_t
+isc_lex_openbuffer(isc_lex_t *lex, isc_buffer_t *buffer) {
+ char name[128];
+
+ /*
+ * Make 'buffer' the current input source for 'lex'.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ snprintf(name, sizeof(name), "buffer-%p", buffer);
+
+ return (new_source(lex, false, false, buffer, name));
+}
+
+isc_result_t
+isc_lex_close(isc_lex_t *lex) {
+ inputsource *source;
+
+ /*
+ * Close the most recently opened object (i.e. file or buffer).
+ */
+
+ REQUIRE(VALID_LEX(lex));
+
+ source = HEAD(lex->sources);
+ if (source == NULL) {
+ return (ISC_R_NOMORE);
+ }
+
+ ISC_LIST_UNLINK(lex->sources, source, link);
+ lex->last_was_eol = source->last_was_eol;
+ if (source->is_file) {
+ if (source->need_close) {
+ (void)fclose((FILE *)(source->input));
+ }
+ }
+ isc_mem_free(lex->mctx, source->name);
+ isc_buffer_free(&source->pushback);
+ isc_mem_put(lex->mctx, source, sizeof(*source));
+
+ return (ISC_R_SUCCESS);
+}
+
+typedef enum {
+ lexstate_start,
+ lexstate_crlf,
+ lexstate_string,
+ lexstate_number,
+ lexstate_maybecomment,
+ lexstate_ccomment,
+ lexstate_ccommentend,
+ lexstate_eatline,
+ lexstate_qstring,
+ lexstate_btext,
+ lexstate_vpair,
+ lexstate_vpairstart,
+ lexstate_qvpair,
+} lexstate;
+
+#define IWSEOL (ISC_LEXOPT_INITIALWS | ISC_LEXOPT_EOL)
+
+static void
+pushback(inputsource *source, int c) {
+ REQUIRE(source->pushback->current > 0);
+ if (c == EOF) {
+ source->at_eof = false;
+ return;
+ }
+ source->pushback->current--;
+ if (c == '\n') {
+ source->line--;
+ }
+}
+
+static isc_result_t
+pushandgrow(isc_lex_t *lex, inputsource *source, int c) {
+ if (isc_buffer_availablelength(source->pushback) == 0) {
+ isc_buffer_t *tbuf = NULL;
+ unsigned int oldlen;
+ isc_region_t used;
+ isc_result_t result;
+
+ oldlen = isc_buffer_length(source->pushback);
+ isc_buffer_allocate(lex->mctx, &tbuf, oldlen * 2);
+ isc_buffer_usedregion(source->pushback, &used);
+ result = isc_buffer_copyregion(tbuf, &used);
+ INSIST(result == ISC_R_SUCCESS);
+ tbuf->current = source->pushback->current;
+ isc_buffer_free(&source->pushback);
+ source->pushback = tbuf;
+ }
+ isc_buffer_putuint8(source->pushback, (uint8_t)c);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_lex_gettoken(isc_lex_t *lex, unsigned int options, isc_token_t *tokenp) {
+ inputsource *source;
+ int c;
+ bool done = false;
+ bool no_comments = false;
+ bool escaped = false;
+ lexstate state = lexstate_start;
+ lexstate saved_state = lexstate_start;
+ isc_buffer_t *buffer;
+ FILE *stream;
+ char *curr, *prev;
+ size_t remaining;
+ uint32_t as_ulong;
+ unsigned int saved_options;
+ isc_result_t result;
+
+ /*
+ * Get the next token.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+ REQUIRE(tokenp != NULL);
+
+ if (source == NULL) {
+ if ((options & ISC_LEXOPT_NOMORE) != 0) {
+ tokenp->type = isc_tokentype_nomore;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_NOMORE);
+ }
+
+ if (source->result != ISC_R_SUCCESS) {
+ return (source->result);
+ }
+
+ lex->saved_paren_count = lex->paren_count;
+ source->saved_line = source->line;
+
+ if (isc_buffer_remaininglength(source->pushback) == 0 && source->at_eof)
+ {
+ if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 &&
+ lex->paren_count != 0)
+ {
+ lex->paren_count = 0;
+ return (ISC_R_UNBALANCED);
+ }
+ if ((options & ISC_LEXOPT_BTEXT) != 0 && lex->brace_count != 0)
+ {
+ lex->brace_count = 0;
+ return (ISC_R_UNBALANCED);
+ }
+ if ((options & ISC_LEXOPT_EOF) != 0) {
+ tokenp->type = isc_tokentype_eof;
+ return (ISC_R_SUCCESS);
+ }
+ return (ISC_R_EOF);
+ }
+
+ isc_buffer_compact(source->pushback);
+
+ saved_options = options;
+ if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 && lex->paren_count > 0) {
+ options &= ~IWSEOL;
+ }
+
+ curr = lex->data;
+ *curr = '\0';
+
+ prev = NULL;
+ remaining = lex->max_token;
+
+#ifdef HAVE_FLOCKFILE
+ if (source->is_file) {
+ flockfile(source->input);
+ }
+#endif /* ifdef HAVE_FLOCKFILE */
+
+ do {
+ if (isc_buffer_remaininglength(source->pushback) == 0) {
+ if (source->is_file) {
+ stream = source->input;
+
+#if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED)
+ c = getc_unlocked(stream);
+#else /* if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED) */
+ c = getc(stream);
+#endif /* if defined(HAVE_FLOCKFILE) && defined(HAVE_GETC_UNLOCKED) */
+ if (c == EOF) {
+ if (ferror(stream)) {
+ source->result =
+ isc__errno2result(
+ errno);
+ result = source->result;
+ goto done;
+ }
+ source->at_eof = true;
+ }
+ } else {
+ buffer = source->input;
+
+ if (buffer->current == buffer->used) {
+ c = EOF;
+ source->at_eof = true;
+ } else {
+ c = *((unsigned char *)buffer->base +
+ buffer->current);
+ buffer->current++;
+ }
+ }
+ if (c != EOF) {
+ source->result = pushandgrow(lex, source, c);
+ if (source->result != ISC_R_SUCCESS) {
+ result = source->result;
+ goto done;
+ }
+ }
+ }
+
+ if (!source->at_eof) {
+ if (state == lexstate_start) {
+ /* Token has not started yet. */
+ source->ignored = isc_buffer_consumedlength(
+ source->pushback);
+ }
+ c = isc_buffer_getuint8(source->pushback);
+ } else {
+ c = EOF;
+ }
+
+ if (c == '\n') {
+ source->line++;
+ }
+
+ if (lex->comment_ok && !no_comments) {
+ if (!escaped && c == ';' &&
+ ((lex->comments & ISC_LEXCOMMENT_DNSMASTERFILE) !=
+ 0))
+ {
+ saved_state = state;
+ state = lexstate_eatline;
+ no_comments = true;
+ continue;
+ } else if (c == '/' &&
+ (lex->comments &
+ (ISC_LEXCOMMENT_C |
+ ISC_LEXCOMMENT_CPLUSPLUS)) != 0)
+ {
+ saved_state = state;
+ state = lexstate_maybecomment;
+ no_comments = true;
+ continue;
+ } else if (c == '#' && ((lex->comments &
+ ISC_LEXCOMMENT_SHELL) != 0))
+ {
+ saved_state = state;
+ state = lexstate_eatline;
+ no_comments = true;
+ continue;
+ }
+ }
+
+ no_read:
+ /* INSIST(c == EOF || (c >= 0 && c <= 255)); */
+ switch (state) {
+ case lexstate_start:
+ if (c == EOF) {
+ lex->last_was_eol = false;
+ if ((options & ISC_LEXOPT_DNSMULTILINE) != 0 &&
+ lex->paren_count != 0)
+ {
+ lex->paren_count = 0;
+ result = ISC_R_UNBALANCED;
+ goto done;
+ }
+ if ((options & ISC_LEXOPT_BTEXT) != 0 &&
+ lex->brace_count != 0)
+ {
+ lex->brace_count = 0;
+ result = ISC_R_UNBALANCED;
+ goto done;
+ }
+ if ((options & ISC_LEXOPT_EOF) == 0) {
+ result = ISC_R_EOF;
+ goto done;
+ }
+ tokenp->type = isc_tokentype_eof;
+ done = true;
+ } else if (c == ' ' || c == '\t') {
+ if (lex->last_was_eol &&
+ (options & ISC_LEXOPT_INITIALWS) != 0)
+ {
+ lex->last_was_eol = false;
+ tokenp->type = isc_tokentype_initialws;
+ tokenp->value.as_char = c;
+ done = true;
+ }
+ } else if (c == '\n') {
+ if ((options & ISC_LEXOPT_EOL) != 0) {
+ tokenp->type = isc_tokentype_eol;
+ done = true;
+ }
+ lex->last_was_eol = true;
+ } else if (c == '\r') {
+ if ((options & ISC_LEXOPT_EOL) != 0) {
+ state = lexstate_crlf;
+ }
+ } else if (c == '"' &&
+ (options & ISC_LEXOPT_QSTRING) != 0)
+ {
+ lex->last_was_eol = false;
+ no_comments = true;
+ state = lexstate_qstring;
+ } else if (lex->specials[c]) {
+ lex->last_was_eol = false;
+ if ((c == '(' || c == ')') &&
+ (options & ISC_LEXOPT_DNSMULTILINE) != 0)
+ {
+ if (c == '(') {
+ if (lex->paren_count == 0) {
+ options &= ~IWSEOL;
+ }
+ lex->paren_count++;
+ } else {
+ if (lex->paren_count == 0) {
+ result =
+ ISC_R_UNBALANCED;
+ goto done;
+ }
+ lex->paren_count--;
+ if (lex->paren_count == 0) {
+ options = saved_options;
+ }
+ }
+ continue;
+ } else if (c == '{' &&
+ (options & ISC_LEXOPT_BTEXT) != 0)
+ {
+ if (lex->brace_count != 0) {
+ result = ISC_R_UNBALANCED;
+ goto done;
+ }
+ lex->brace_count++;
+ options &= ~IWSEOL;
+ state = lexstate_btext;
+ no_comments = true;
+ continue;
+ }
+ tokenp->type = isc_tokentype_special;
+ tokenp->value.as_char = c;
+ done = true;
+ } else if (isdigit((unsigned char)c) &&
+ (options & ISC_LEXOPT_NUMBER) != 0)
+ {
+ lex->last_was_eol = false;
+ if ((options & ISC_LEXOPT_OCTAL) != 0 &&
+ (c == '8' || c == '9'))
+ {
+ state = lexstate_string;
+ } else {
+ state = lexstate_number;
+ }
+ goto no_read;
+ } else {
+ lex->last_was_eol = false;
+ state = lexstate_string;
+ goto no_read;
+ }
+ break;
+ case lexstate_crlf:
+ if (c != '\n') {
+ pushback(source, c);
+ }
+ tokenp->type = isc_tokentype_eol;
+ done = true;
+ lex->last_was_eol = true;
+ break;
+ case lexstate_number:
+ if (c == EOF || !isdigit((unsigned char)c)) {
+ if (c == ' ' || c == '\t' || c == '\r' ||
+ c == '\n' || c == EOF || lex->specials[c])
+ {
+ int base;
+ if ((options & ISC_LEXOPT_OCTAL) != 0) {
+ base = 8;
+ } else if ((options &
+ ISC_LEXOPT_CNUMBER) != 0)
+ {
+ base = 0;
+ } else {
+ base = 10;
+ }
+ pushback(source, c);
+
+ result = isc_parse_uint32(
+ &as_ulong, lex->data, base);
+ if (result == ISC_R_SUCCESS) {
+ tokenp->type =
+ isc_tokentype_number;
+ tokenp->value.as_ulong =
+ as_ulong;
+ } else if (result == ISC_R_BADNUMBER) {
+ isc_tokenvalue_t *v;
+
+ tokenp->type =
+ isc_tokentype_string;
+ v = &(tokenp->value);
+ v->as_textregion.base =
+ lex->data;
+ v->as_textregion.length =
+ (unsigned int)(lex->max_token -
+ remaining);
+ } else {
+ goto done;
+ }
+ done = true;
+ continue;
+ } else if ((options & ISC_LEXOPT_CNUMBER) ==
+ 0 ||
+ ((c != 'x' && c != 'X') ||
+ (curr != &lex->data[1]) ||
+ (lex->data[0] != '0')))
+ {
+ /* Above test supports hex numbers */
+ state = lexstate_string;
+ }
+ } else if ((options & ISC_LEXOPT_OCTAL) != 0 &&
+ (c == '8' || c == '9'))
+ {
+ state = lexstate_string;
+ }
+ if (remaining == 0U) {
+ result = grow_data(lex, &remaining, &curr,
+ &prev);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ INSIST(remaining > 0U);
+ *curr++ = c;
+ *curr = '\0';
+ remaining--;
+ break;
+ case lexstate_string:
+ if (!escaped && c == '=' &&
+ (options & ISC_LEXOPT_VPAIR) != 0)
+ {
+ if (remaining == 0U) {
+ result = grow_data(lex, &remaining,
+ &curr, &prev);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ INSIST(remaining > 0U);
+ *curr++ = c;
+ *curr = '\0';
+ remaining--;
+ state = lexstate_vpairstart;
+ break;
+ }
+ FALLTHROUGH;
+ case lexstate_vpairstart:
+ if (state == lexstate_vpairstart) {
+ if (c == '"' &&
+ (options & ISC_LEXOPT_QVPAIR) != 0)
+ {
+ no_comments = true;
+ state = lexstate_qvpair;
+ break;
+ }
+ state = lexstate_vpair;
+ }
+ FALLTHROUGH;
+ case lexstate_vpair:
+ /*
+ * EOF needs to be checked before lex->specials[c]
+ * as lex->specials[EOF] is not a good idea.
+ */
+ if (c == '\r' || c == '\n' || c == EOF ||
+ (!escaped &&
+ (c == ' ' || c == '\t' || lex->specials[c])))
+ {
+ pushback(source, c);
+ if (source->result != ISC_R_SUCCESS) {
+ result = source->result;
+ goto done;
+ }
+ if (escaped && c == EOF) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ tokenp->type = (state == lexstate_string)
+ ? isc_tokentype_string
+ : isc_tokentype_vpair;
+ tokenp->value.as_textregion.base = lex->data;
+ tokenp->value.as_textregion.length =
+ (unsigned int)(lex->max_token -
+ remaining);
+ done = true;
+ continue;
+ }
+ if ((options & ISC_LEXOPT_ESCAPE) != 0) {
+ escaped = (!escaped && c == '\\') ? true
+ : false;
+ }
+ if (remaining == 0U) {
+ result = grow_data(lex, &remaining, &curr,
+ &prev);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ INSIST(remaining > 0U);
+ *curr++ = c;
+ *curr = '\0';
+ remaining--;
+ break;
+ case lexstate_maybecomment:
+ if (c == '*' && (lex->comments & ISC_LEXCOMMENT_C) != 0)
+ {
+ state = lexstate_ccomment;
+ continue;
+ } else if (c == '/' && (lex->comments &
+ ISC_LEXCOMMENT_CPLUSPLUS) != 0)
+ {
+ state = lexstate_eatline;
+ continue;
+ }
+ pushback(source, c);
+ c = '/';
+ no_comments = false;
+ state = saved_state;
+ goto no_read;
+ case lexstate_ccomment:
+ if (c == EOF) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ if (c == '*') {
+ state = lexstate_ccommentend;
+ }
+ break;
+ case lexstate_ccommentend:
+ if (c == EOF) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ if (c == '/') {
+ /*
+ * C-style comments become a single space.
+ * We do this to ensure that a comment will
+ * act as a delimiter for strings and
+ * numbers.
+ */
+ c = ' ';
+ no_comments = false;
+ state = saved_state;
+ goto no_read;
+ } else if (c != '*') {
+ state = lexstate_ccomment;
+ }
+ break;
+ case lexstate_eatline:
+ if ((c == '\n') || (c == EOF)) {
+ no_comments = false;
+ state = saved_state;
+ goto no_read;
+ }
+ break;
+ case lexstate_qstring:
+ case lexstate_qvpair:
+ if (c == EOF) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ if (c == '"') {
+ if (escaped) {
+ escaped = false;
+ /*
+ * Overwrite the preceding backslash.
+ */
+ INSIST(prev != NULL);
+ *prev = '"';
+ } else {
+ tokenp->type =
+ (state == lexstate_qstring)
+ ? isc_tokentype_qstring
+ : isc_tokentype_qvpair;
+ tokenp->value.as_textregion.base =
+ lex->data;
+ tokenp->value.as_textregion.length =
+ (unsigned int)(lex->max_token -
+ remaining);
+ no_comments = false;
+ done = true;
+ }
+ } else {
+ if (c == '\n' && !escaped &&
+ (options & ISC_LEXOPT_QSTRINGMULTILINE) ==
+ 0)
+ {
+ pushback(source, c);
+ result = ISC_R_UNBALANCEDQUOTES;
+ goto done;
+ }
+ if (c == '\\' && !escaped) {
+ escaped = true;
+ } else {
+ escaped = false;
+ }
+ if (remaining == 0U) {
+ result = grow_data(lex, &remaining,
+ &curr, &prev);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ INSIST(remaining > 0U);
+ prev = curr;
+ *curr++ = c;
+ *curr = '\0';
+ remaining--;
+ }
+ break;
+ case lexstate_btext:
+ if (c == EOF) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ if (c == '{') {
+ if (escaped) {
+ escaped = false;
+ } else {
+ lex->brace_count++;
+ }
+ } else if (c == '}') {
+ if (escaped) {
+ escaped = false;
+ } else {
+ INSIST(lex->brace_count > 0);
+ lex->brace_count--;
+ }
+
+ if (lex->brace_count == 0) {
+ tokenp->type = isc_tokentype_btext;
+ tokenp->value.as_textregion.base =
+ lex->data;
+ tokenp->value.as_textregion.length =
+ (unsigned int)(lex->max_token -
+ remaining);
+ no_comments = false;
+ done = true;
+ break;
+ }
+ }
+
+ if (c == '\\' && !escaped) {
+ escaped = true;
+ } else {
+ escaped = false;
+ }
+
+ if (remaining == 0U) {
+ result = grow_data(lex, &remaining, &curr,
+ &prev);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ }
+ INSIST(remaining > 0U);
+ prev = curr;
+ *curr++ = c;
+ *curr = '\0';
+ remaining--;
+ break;
+ default:
+ FATAL_ERROR("Unexpected state %d", state);
+ }
+ } while (!done);
+
+ result = ISC_R_SUCCESS;
+done:
+#ifdef HAVE_FLOCKFILE
+ if (source->is_file) {
+ funlockfile(source->input);
+ }
+#endif /* ifdef HAVE_FLOCKFILE */
+ return (result);
+}
+
+isc_result_t
+isc_lex_getmastertoken(isc_lex_t *lex, isc_token_t *token,
+ isc_tokentype_t expect, bool eol) {
+ unsigned int options = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF |
+ ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE;
+ isc_result_t result;
+
+ if (expect == isc_tokentype_vpair) {
+ options |= ISC_LEXOPT_VPAIR;
+ } else if (expect == isc_tokentype_qvpair) {
+ options |= ISC_LEXOPT_VPAIR;
+ options |= ISC_LEXOPT_QVPAIR;
+ } else if (expect == isc_tokentype_qstring) {
+ options |= ISC_LEXOPT_QSTRING;
+ } else if (expect == isc_tokentype_number) {
+ options |= ISC_LEXOPT_NUMBER;
+ }
+ result = isc_lex_gettoken(lex, options, token);
+ if (result == ISC_R_RANGE) {
+ isc_lex_ungettoken(lex, token);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (eol && ((token->type == isc_tokentype_eol) ||
+ (token->type == isc_tokentype_eof)))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ if (token->type == isc_tokentype_string &&
+ (expect == isc_tokentype_qstring || expect == isc_tokentype_qvpair))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ if (token->type == isc_tokentype_vpair &&
+ expect == isc_tokentype_qvpair)
+ {
+ return (ISC_R_SUCCESS);
+ }
+ if (token->type != expect) {
+ isc_lex_ungettoken(lex, token);
+ if (token->type == isc_tokentype_eol ||
+ token->type == isc_tokentype_eof)
+ {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ if (expect == isc_tokentype_number) {
+ return (ISC_R_BADNUMBER);
+ }
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_lex_getoctaltoken(isc_lex_t *lex, isc_token_t *token, bool eol) {
+ unsigned int options = ISC_LEXOPT_EOL | ISC_LEXOPT_EOF |
+ ISC_LEXOPT_DNSMULTILINE | ISC_LEXOPT_ESCAPE |
+ ISC_LEXOPT_NUMBER | ISC_LEXOPT_OCTAL;
+ isc_result_t result;
+
+ result = isc_lex_gettoken(lex, options, token);
+ if (result == ISC_R_RANGE) {
+ isc_lex_ungettoken(lex, token);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (eol && ((token->type == isc_tokentype_eol) ||
+ (token->type == isc_tokentype_eof)))
+ {
+ return (ISC_R_SUCCESS);
+ }
+ if (token->type != isc_tokentype_number) {
+ isc_lex_ungettoken(lex, token);
+ if (token->type == isc_tokentype_eol ||
+ token->type == isc_tokentype_eof)
+ {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ return (ISC_R_BADNUMBER);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_lex_ungettoken(isc_lex_t *lex, isc_token_t *tokenp) {
+ inputsource *source;
+ /*
+ * Unget the current token.
+ */
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+ REQUIRE(source != NULL);
+ REQUIRE(tokenp != NULL);
+ REQUIRE(isc_buffer_consumedlength(source->pushback) != 0 ||
+ tokenp->type == isc_tokentype_eof);
+
+ UNUSED(tokenp);
+
+ isc_buffer_first(source->pushback);
+ lex->paren_count = lex->saved_paren_count;
+ source->line = source->saved_line;
+ source->at_eof = false;
+}
+
+void
+isc_lex_getlasttokentext(isc_lex_t *lex, isc_token_t *tokenp, isc_region_t *r) {
+ inputsource *source;
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+ REQUIRE(source != NULL);
+ REQUIRE(tokenp != NULL);
+ REQUIRE(isc_buffer_consumedlength(source->pushback) != 0 ||
+ tokenp->type == isc_tokentype_eof);
+
+ UNUSED(tokenp);
+
+ INSIST(source->ignored <= isc_buffer_consumedlength(source->pushback));
+ r->base = (unsigned char *)isc_buffer_base(source->pushback) +
+ source->ignored;
+ r->length = isc_buffer_consumedlength(source->pushback) -
+ source->ignored;
+}
+
+char *
+isc_lex_getsourcename(isc_lex_t *lex) {
+ inputsource *source;
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+
+ if (source == NULL) {
+ return (NULL);
+ }
+
+ return (source->name);
+}
+
+unsigned long
+isc_lex_getsourceline(isc_lex_t *lex) {
+ inputsource *source;
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+
+ if (source == NULL) {
+ return (0);
+ }
+
+ return (source->line);
+}
+
+isc_result_t
+isc_lex_setsourcename(isc_lex_t *lex, const char *name) {
+ inputsource *source;
+ char *newname;
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+
+ if (source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ newname = isc_mem_strdup(lex->mctx, name);
+ isc_mem_free(lex->mctx, source->name);
+ source->name = newname;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_lex_setsourceline(isc_lex_t *lex, unsigned long line) {
+ inputsource *source;
+
+ REQUIRE(VALID_LEX(lex));
+ source = HEAD(lex->sources);
+
+ if (source == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ source->line = line;
+ return (ISC_R_SUCCESS);
+}
+
+bool
+isc_lex_isfile(isc_lex_t *lex) {
+ inputsource *source;
+
+ REQUIRE(VALID_LEX(lex));
+
+ source = HEAD(lex->sources);
+
+ if (source == NULL) {
+ return (false);
+ }
+
+ return (source->is_file);
+}
diff --git a/lib/isc/lib.c b/lib/isc/lib.c
new file mode 100644
index 0000000..343fedf
--- /dev/null
+++ b/lib/isc/lib.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/mem.h>
+#include <isc/os.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include "config.h"
+#include "mem_p.h"
+#include "os_p.h"
+#include "tls_p.h"
+#include "trampoline_p.h"
+
+#ifndef ISC_CONSTRUCTOR
+#error Either __attribute__((constructor|destructor))__ or DllMain support needed to compile BIND 9.
+#endif
+
+/***
+ *** Functions
+ ***/
+
+void
+isc__initialize(void) ISC_CONSTRUCTOR;
+void
+isc__shutdown(void) ISC_DESTRUCTOR;
+
+void
+isc__initialize(void) {
+ isc__os_initialize();
+ isc__mem_initialize();
+ isc__tls_initialize();
+ isc__trampoline_initialize();
+ (void)isc_os_ncpus();
+}
+
+void
+isc__shutdown(void) {
+ isc__trampoline_shutdown();
+ isc__tls_shutdown();
+ isc__mem_shutdown();
+ isc__os_shutdown();
+}
diff --git a/lib/isc/log.c b/lib/isc/log.c
new file mode 100644
index 0000000..523fd2d
--- /dev/null
+++ b/lib/isc/log.c
@@ -0,0 +1,1909 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/types.h> /* dev_t FreeBSD 2.1 */
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/atomic.h>
+#include <isc/dir.h>
+#include <isc/errno.h>
+#include <isc/file.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/rwlock.h>
+#include <isc/stat.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#define LCTX_MAGIC ISC_MAGIC('L', 'c', 't', 'x')
+#define VALID_CONTEXT(lctx) ISC_MAGIC_VALID(lctx, LCTX_MAGIC)
+
+#define LCFG_MAGIC ISC_MAGIC('L', 'c', 'f', 'g')
+#define VALID_CONFIG(lcfg) ISC_MAGIC_VALID(lcfg, LCFG_MAGIC)
+
+#define RDLOCK(lp) RWLOCK(lp, isc_rwlocktype_read);
+#define WRLOCK(lp) RWLOCK(lp, isc_rwlocktype_write);
+#define RDUNLOCK(lp) RWUNLOCK(lp, isc_rwlocktype_read);
+#define WRUNLOCK(lp) RWUNLOCK(lp, isc_rwlocktype_write);
+
+static thread_local bool forcelog = false;
+
+/*
+ * XXXDCL make dynamic?
+ */
+#define LOG_BUFFER_SIZE (8 * 1024)
+
+/*!
+ * This is the structure that holds each named channel. A simple linked
+ * list chains all of the channels together, so an individual channel is
+ * found by doing strcmp()s with the names down the list. Their should
+ * be no performance penalty from this as it is expected that the number
+ * of named channels will be no more than a dozen or so, and name lookups
+ * from the head of the list are only done when isc_log_usechannel() is
+ * called, which should also be very infrequent.
+ */
+typedef struct isc_logchannel isc_logchannel_t;
+
+struct isc_logchannel {
+ char *name;
+ unsigned int type;
+ int level;
+ unsigned int flags;
+ isc_logdestination_t destination;
+ ISC_LINK(isc_logchannel_t) link;
+};
+
+/*!
+ * The logchannellist structure associates categories and modules with
+ * channels. First the appropriate channellist is found based on the
+ * category, and then each structure in the linked list is checked for
+ * a matching module. It is expected that the number of channels
+ * associated with any given category will be very short, no more than
+ * three or four in the more unusual cases.
+ */
+typedef struct isc_logchannellist isc_logchannellist_t;
+
+struct isc_logchannellist {
+ const isc_logmodule_t *module;
+ isc_logchannel_t *channel;
+ ISC_LINK(isc_logchannellist_t) link;
+};
+
+/*!
+ * This structure is used to remember messages for pruning via
+ * isc_log_[v]write1().
+ */
+typedef struct isc_logmessage isc_logmessage_t;
+
+struct isc_logmessage {
+ char *text;
+ isc_time_t time;
+ ISC_LINK(isc_logmessage_t) link;
+};
+
+/*!
+ * The isc_logconfig structure is used to store the configurable information
+ * about where messages are actually supposed to be sent -- the information
+ * that could changed based on some configuration file, as opposed to the
+ * the category/module specification of isc_log_[v]write[1] that is compiled
+ * into a program, or the debug_level which is dynamic state information.
+ */
+struct isc_logconfig {
+ unsigned int magic;
+ isc_log_t *lctx;
+ ISC_LIST(isc_logchannel_t) channels;
+ ISC_LIST(isc_logchannellist_t) * channellists;
+ unsigned int channellist_count;
+ unsigned int duplicate_interval;
+ int_fast32_t highest_level;
+ char *tag;
+ bool dynamic;
+};
+
+/*!
+ * This isc_log structure provides the context for the isc_log functions.
+ * The log context locks itself in isc_log_doit, the internal backend to
+ * isc_log_write. The locking is necessary both to provide exclusive access
+ * to the buffer into which the message is formatted and to guard against
+ * competing threads trying to write to the same syslog resource. (On
+ * some systems, such as BSD/OS, stdio is thread safe but syslog is not.)
+ * Unfortunately, the lock cannot guard against a _different_ logging
+ * context in the same program competing for syslog's attention. Thus
+ * There Can Be Only One, but this is not enforced.
+ * XXXDCL enforce it?
+ *
+ * Note that the category and module information is not locked.
+ * This is because in the usual case, only one isc_log_t is ever created
+ * in a program, and the category/module registration happens only once.
+ * XXXDCL it might be wise to add more locking overall.
+ */
+struct isc_log {
+ /* Not locked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_logcategory_t *categories;
+ unsigned int category_count;
+ isc_logmodule_t *modules;
+ unsigned int module_count;
+ atomic_int_fast32_t debug_level;
+ isc_rwlock_t lcfg_rwl;
+ /* Locked by isc_log lcfg_rwl */
+ isc_logconfig_t *logconfig;
+ isc_mutex_t lock;
+ /* Locked by isc_log lock. */
+ char buffer[LOG_BUFFER_SIZE];
+ ISC_LIST(isc_logmessage_t) messages;
+ atomic_bool dynamic;
+ atomic_int_fast32_t highest_level;
+};
+
+/*!
+ * Used when ISC_LOG_PRINTLEVEL is enabled for a channel.
+ */
+static const char *log_level_strings[] = { "debug", "info", "notice",
+ "warning", "error", "critical" };
+
+/*!
+ * Used to convert ISC_LOG_* priorities into syslog priorities.
+ * XXXDCL This will need modification for NT.
+ */
+static const int syslog_map[] = { LOG_DEBUG, LOG_INFO, LOG_NOTICE,
+ LOG_WARNING, LOG_ERR, LOG_CRIT };
+
+/*!
+ * When adding new categories, a corresponding ISC_LOGCATEGORY_foo
+ * definition needs to be added to <isc/log.h>.
+ *
+ * The default category is provided so that the internal default can
+ * be overridden. Since the default is always looked up as the first
+ * channellist in the log context, it must come first in isc_categories[].
+ */
+isc_logcategory_t isc_categories[] = { { "default", 0 }, /* "default
+ must come
+ first. */
+ { "general", 0 },
+ { "sslkeylog", 0 },
+ { NULL, 0 } };
+
+/*!
+ * See above comment for categories, and apply it to modules.
+ */
+isc_logmodule_t isc_modules[] = { { "socket", 0 }, { "time", 0 },
+ { "interface", 0 }, { "timer", 0 },
+ { "file", 0 }, { "netmgr", 0 },
+ { "other", 0 }, { NULL, 0 } };
+
+/*!
+ * This essentially constant structure must be filled in at run time,
+ * because its channel member is pointed to a channel that is created
+ * dynamically with isc_log_createchannel.
+ */
+static isc_logchannellist_t default_channel;
+
+/*!
+ * libisc logs to this context.
+ */
+isc_log_t *isc_lctx = NULL;
+
+/*!
+ * Forward declarations.
+ */
+static void
+assignchannel(isc_logconfig_t *lcfg, unsigned int category_id,
+ const isc_logmodule_t *module, isc_logchannel_t *channel);
+
+static void
+sync_channellist(isc_logconfig_t *lcfg);
+
+static void
+sync_highest_level(isc_log_t *lctx, isc_logconfig_t *lcfg);
+
+static isc_result_t
+greatest_version(isc_logfile_t *file, int versions, int *greatest);
+
+static void
+isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, bool write_once,
+ const char *format, va_list args) ISC_FORMAT_PRINTF(6, 0);
+
+/*@{*/
+/*!
+ * Convenience macros.
+ */
+
+#define FACILITY(channel) (channel->destination.facility)
+#define FILE_NAME(channel) (channel->destination.file.name)
+#define FILE_STREAM(channel) (channel->destination.file.stream)
+#define FILE_VERSIONS(channel) (channel->destination.file.versions)
+#define FILE_SUFFIX(channel) (channel->destination.file.suffix)
+#define FILE_MAXSIZE(channel) (channel->destination.file.maximum_size)
+#define FILE_MAXREACHED(channel) (channel->destination.file.maximum_reached)
+
+/*@}*/
+/****
+**** Public interfaces.
+****/
+
+/*
+ * Establish a new logging context, with default channels.
+ */
+void
+isc_log_create(isc_mem_t *mctx, isc_log_t **lctxp, isc_logconfig_t **lcfgp) {
+ isc_log_t *lctx;
+ isc_logconfig_t *lcfg = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(lctxp != NULL && *lctxp == NULL);
+ REQUIRE(lcfgp == NULL || *lcfgp == NULL);
+
+ lctx = isc_mem_get(mctx, sizeof(*lctx));
+ lctx->mctx = NULL;
+ isc_mem_attach(mctx, &lctx->mctx);
+ lctx->categories = NULL;
+ lctx->category_count = 0;
+ lctx->modules = NULL;
+ lctx->module_count = 0;
+ atomic_init(&lctx->debug_level, 0);
+
+ ISC_LIST_INIT(lctx->messages);
+
+ isc_mutex_init(&lctx->lock);
+ isc_rwlock_init(&lctx->lcfg_rwl, 0, 0);
+
+ /*
+ * Normally setting the magic number is the last step done
+ * in a creation function, but a valid log context is needed
+ * by isc_log_registercategories and isc_logconfig_create.
+ * If either fails, the lctx is destroyed and not returned
+ * to the caller.
+ */
+ lctx->magic = LCTX_MAGIC;
+
+ isc_log_registercategories(lctx, isc_categories);
+ isc_log_registermodules(lctx, isc_modules);
+ isc_logconfig_create(lctx, &lcfg);
+
+ sync_channellist(lcfg);
+
+ lctx->logconfig = lcfg;
+
+ atomic_init(&lctx->highest_level, lcfg->highest_level);
+ atomic_init(&lctx->dynamic, lcfg->dynamic);
+
+ *lctxp = lctx;
+ if (lcfgp != NULL) {
+ *lcfgp = lcfg;
+ }
+}
+
+void
+isc_logconfig_create(isc_log_t *lctx, isc_logconfig_t **lcfgp) {
+ isc_logconfig_t *lcfg;
+ isc_logdestination_t destination;
+ int level = ISC_LOG_INFO;
+
+ REQUIRE(lcfgp != NULL && *lcfgp == NULL);
+ REQUIRE(VALID_CONTEXT(lctx));
+
+ lcfg = isc_mem_get(lctx->mctx, sizeof(*lcfg));
+
+ lcfg->lctx = lctx;
+ lcfg->channellists = NULL;
+ lcfg->channellist_count = 0;
+ lcfg->duplicate_interval = 0;
+ lcfg->highest_level = level;
+ lcfg->tag = NULL;
+ lcfg->dynamic = false;
+ ISC_LIST_INIT(lcfg->channels);
+ lcfg->magic = LCFG_MAGIC;
+
+ /*
+ * Create the default channels:
+ * default_syslog, default_stderr, default_debug and null.
+ */
+ destination.facility = LOG_DAEMON;
+ isc_log_createchannel(lcfg, "default_syslog", ISC_LOG_TOSYSLOG, level,
+ &destination, 0);
+
+ destination.file.stream = stderr;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.suffix = isc_log_rollsuffix_increment;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(lcfg, "default_stderr", ISC_LOG_TOFILEDESC, level,
+ &destination, ISC_LOG_PRINTTIME);
+
+ /*
+ * Set the default category's channel to default_stderr,
+ * which is at the head of the channels list because it was
+ * just created.
+ */
+ default_channel.channel = ISC_LIST_HEAD(lcfg->channels);
+
+ destination.file.stream = stderr;
+ destination.file.name = NULL;
+ destination.file.versions = ISC_LOG_ROLLNEVER;
+ destination.file.suffix = isc_log_rollsuffix_increment;
+ destination.file.maximum_size = 0;
+ isc_log_createchannel(lcfg, "default_debug", ISC_LOG_TOFILEDESC,
+ ISC_LOG_DYNAMIC, &destination, ISC_LOG_PRINTTIME);
+
+ isc_log_createchannel(lcfg, "null", ISC_LOG_TONULL, ISC_LOG_DYNAMIC,
+ NULL, 0);
+
+ *lcfgp = lcfg;
+}
+
+void
+isc_logconfig_use(isc_log_t *lctx, isc_logconfig_t *lcfg) {
+ isc_logconfig_t *old_cfg;
+
+ REQUIRE(VALID_CONTEXT(lctx));
+ REQUIRE(VALID_CONFIG(lcfg));
+ REQUIRE(lcfg->lctx == lctx);
+
+ /*
+ * Ensure that lcfg->channellist_count == lctx->category_count.
+ * They won't be equal if isc_log_usechannel has not been called
+ * since any call to isc_log_registercategories.
+ */
+ sync_channellist(lcfg);
+
+ WRLOCK(&lctx->lcfg_rwl);
+ old_cfg = lctx->logconfig;
+ lctx->logconfig = lcfg;
+ sync_highest_level(lctx, lcfg);
+ WRUNLOCK(&lctx->lcfg_rwl);
+
+ isc_logconfig_destroy(&old_cfg);
+}
+
+void
+isc_log_destroy(isc_log_t **lctxp) {
+ isc_log_t *lctx;
+ isc_logconfig_t *lcfg;
+ isc_mem_t *mctx;
+ isc_logmessage_t *message;
+
+ REQUIRE(lctxp != NULL && VALID_CONTEXT(*lctxp));
+
+ lctx = *lctxp;
+ *lctxp = NULL;
+ mctx = lctx->mctx;
+
+ /* Stop the logging as a first thing */
+ atomic_store_release(&lctx->debug_level, 0);
+ atomic_store_release(&lctx->highest_level, 0);
+ atomic_store_release(&lctx->dynamic, false);
+
+ WRLOCK(&lctx->lcfg_rwl);
+ lcfg = lctx->logconfig;
+ lctx->logconfig = NULL;
+ WRUNLOCK(&lctx->lcfg_rwl);
+
+ if (lcfg != NULL) {
+ isc_logconfig_destroy(&lcfg);
+ }
+
+ isc_rwlock_destroy(&lctx->lcfg_rwl);
+ isc_mutex_destroy(&lctx->lock);
+
+ while ((message = ISC_LIST_HEAD(lctx->messages)) != NULL) {
+ ISC_LIST_UNLINK(lctx->messages, message, link);
+
+ isc_mem_put(mctx, message,
+ sizeof(*message) + strlen(message->text) + 1);
+ }
+
+ lctx->buffer[0] = '\0';
+ lctx->categories = NULL;
+ lctx->category_count = 0;
+ lctx->modules = NULL;
+ lctx->module_count = 0;
+ lctx->mctx = NULL;
+ lctx->magic = 0;
+
+ isc_mem_putanddetach(&mctx, lctx, sizeof(*lctx));
+}
+
+void
+isc_logconfig_destroy(isc_logconfig_t **lcfgp) {
+ isc_logconfig_t *lcfg;
+ isc_mem_t *mctx;
+ isc_logchannel_t *channel;
+ char *filename;
+ unsigned int i;
+
+ REQUIRE(lcfgp != NULL && VALID_CONFIG(*lcfgp));
+
+ lcfg = *lcfgp;
+ *lcfgp = NULL;
+
+ /*
+ * This function cannot be called with a logconfig that is in
+ * use by a log context.
+ */
+ REQUIRE(lcfg->lctx != NULL);
+
+ RDLOCK(&lcfg->lctx->lcfg_rwl);
+ REQUIRE(lcfg->lctx->logconfig != lcfg);
+ RDUNLOCK(&lcfg->lctx->lcfg_rwl);
+
+ mctx = lcfg->lctx->mctx;
+
+ while ((channel = ISC_LIST_HEAD(lcfg->channels)) != NULL) {
+ ISC_LIST_UNLINK(lcfg->channels, channel, link);
+
+ if (channel->type == ISC_LOG_TOFILE) {
+ /*
+ * The filename for the channel may have ultimately
+ * started its life in user-land as a const string,
+ * but in isc_log_createchannel it gets copied
+ * into writable memory and is not longer truly const.
+ */
+ DE_CONST(FILE_NAME(channel), filename);
+ isc_mem_free(mctx, filename);
+
+ if (FILE_STREAM(channel) != NULL) {
+ (void)fclose(FILE_STREAM(channel));
+ }
+ }
+
+ isc_mem_free(mctx, channel->name);
+ isc_mem_put(mctx, channel, sizeof(*channel));
+ }
+
+ for (i = 0; i < lcfg->channellist_count; i++) {
+ isc_logchannellist_t *item;
+ while ((item = ISC_LIST_HEAD(lcfg->channellists[i])) != NULL) {
+ ISC_LIST_UNLINK(lcfg->channellists[i], item, link);
+ isc_mem_put(mctx, item, sizeof(*item));
+ }
+ }
+
+ if (lcfg->channellist_count > 0) {
+ isc_mem_put(mctx, lcfg->channellists,
+ lcfg->channellist_count *
+ sizeof(ISC_LIST(isc_logchannellist_t)));
+ }
+
+ lcfg->dynamic = false;
+ if (lcfg->tag != NULL) {
+ isc_mem_free(lcfg->lctx->mctx, lcfg->tag);
+ }
+ lcfg->tag = NULL;
+ lcfg->highest_level = 0;
+ lcfg->duplicate_interval = 0;
+ lcfg->magic = 0;
+
+ isc_mem_put(mctx, lcfg, sizeof(*lcfg));
+}
+
+void
+isc_log_registercategories(isc_log_t *lctx, isc_logcategory_t categories[]) {
+ isc_logcategory_t *catp;
+
+ REQUIRE(VALID_CONTEXT(lctx));
+ REQUIRE(categories != NULL && categories[0].name != NULL);
+
+ /*
+ * XXXDCL This somewhat sleazy situation of using the last pointer
+ * in one category array to point to the next array exists because
+ * this registration function returns void and I didn't want to have
+ * change everything that used it by making it return an isc_result_t.
+ * It would need to do that if it had to allocate memory to store
+ * pointers to each array passed in.
+ */
+ if (lctx->categories == NULL) {
+ lctx->categories = categories;
+ } else {
+ /*
+ * Adjust the last (NULL) pointer of the already registered
+ * categories to point to the incoming array.
+ */
+ for (catp = lctx->categories; catp->name != NULL;) {
+ if (catp->id == UINT_MAX) {
+ /*
+ * The name pointer points to the next array.
+ * Ick.
+ */
+ DE_CONST(catp->name, catp);
+ } else {
+ catp++;
+ }
+ }
+
+ catp->name = (void *)categories;
+ catp->id = UINT_MAX;
+ }
+
+ /*
+ * Update the id number of the category with its new global id.
+ */
+ for (catp = categories; catp->name != NULL; catp++) {
+ catp->id = lctx->category_count++;
+ }
+}
+
+isc_logcategory_t *
+isc_log_categorybyname(isc_log_t *lctx, const char *name) {
+ isc_logcategory_t *catp;
+
+ REQUIRE(VALID_CONTEXT(lctx));
+ REQUIRE(name != NULL);
+
+ for (catp = lctx->categories; catp->name != NULL;) {
+ if (catp->id == UINT_MAX) {
+ /*
+ * catp is neither modified nor returned to the
+ * caller, so removing its const qualifier is ok.
+ */
+ DE_CONST(catp->name, catp);
+ } else {
+ if (strcmp(catp->name, name) == 0) {
+ return (catp);
+ }
+ catp++;
+ }
+ }
+
+ return (NULL);
+}
+
+void
+isc_log_registermodules(isc_log_t *lctx, isc_logmodule_t modules[]) {
+ isc_logmodule_t *modp;
+
+ REQUIRE(VALID_CONTEXT(lctx));
+ REQUIRE(modules != NULL && modules[0].name != NULL);
+
+ /*
+ * XXXDCL This somewhat sleazy situation of using the last pointer
+ * in one category array to point to the next array exists because
+ * this registration function returns void and I didn't want to have
+ * change everything that used it by making it return an isc_result_t.
+ * It would need to do that if it had to allocate memory to store
+ * pointers to each array passed in.
+ */
+ if (lctx->modules == NULL) {
+ lctx->modules = modules;
+ } else {
+ /*
+ * Adjust the last (NULL) pointer of the already registered
+ * modules to point to the incoming array.
+ */
+ for (modp = lctx->modules; modp->name != NULL;) {
+ if (modp->id == UINT_MAX) {
+ /*
+ * The name pointer points to the next array.
+ * Ick.
+ */
+ DE_CONST(modp->name, modp);
+ } else {
+ modp++;
+ }
+ }
+
+ modp->name = (void *)modules;
+ modp->id = UINT_MAX;
+ }
+
+ /*
+ * Update the id number of the module with its new global id.
+ */
+ for (modp = modules; modp->name != NULL; modp++) {
+ modp->id = lctx->module_count++;
+ }
+}
+
+isc_logmodule_t *
+isc_log_modulebyname(isc_log_t *lctx, const char *name) {
+ isc_logmodule_t *modp;
+
+ REQUIRE(VALID_CONTEXT(lctx));
+ REQUIRE(name != NULL);
+
+ for (modp = lctx->modules; modp->name != NULL;) {
+ if (modp->id == UINT_MAX) {
+ /*
+ * modp is neither modified nor returned to the
+ * caller, so removing its const qualifier is ok.
+ */
+ DE_CONST(modp->name, modp);
+ } else {
+ if (strcmp(modp->name, name) == 0) {
+ return (modp);
+ }
+ modp++;
+ }
+ }
+
+ return (NULL);
+}
+
+void
+isc_log_createchannel(isc_logconfig_t *lcfg, const char *name,
+ unsigned int type, int level,
+ const isc_logdestination_t *destination,
+ unsigned int flags) {
+ isc_logchannel_t *channel;
+ isc_mem_t *mctx;
+ unsigned int permitted = ISC_LOG_PRINTALL | ISC_LOG_DEBUGONLY |
+ ISC_LOG_BUFFERED | ISC_LOG_ISO8601 |
+ ISC_LOG_UTC;
+
+ REQUIRE(VALID_CONFIG(lcfg));
+ REQUIRE(name != NULL);
+ REQUIRE(type == ISC_LOG_TOSYSLOG || type == ISC_LOG_TOFILE ||
+ type == ISC_LOG_TOFILEDESC || type == ISC_LOG_TONULL);
+ REQUIRE(destination != NULL || type == ISC_LOG_TONULL);
+ REQUIRE(level >= ISC_LOG_CRITICAL);
+ REQUIRE((flags & ~permitted) == 0);
+
+ /* XXXDCL find duplicate names? */
+
+ mctx = lcfg->lctx->mctx;
+
+ channel = isc_mem_get(mctx, sizeof(*channel));
+
+ channel->name = isc_mem_strdup(mctx, name);
+
+ channel->type = type;
+ channel->level = level;
+ channel->flags = flags;
+ ISC_LINK_INIT(channel, link);
+
+ switch (type) {
+ case ISC_LOG_TOSYSLOG:
+ FACILITY(channel) = destination->facility;
+ break;
+
+ case ISC_LOG_TOFILE:
+ /*
+ * The file name is copied because greatest_version wants
+ * to scribble on it, so it needs to be definitely in
+ * writable memory.
+ */
+ FILE_NAME(channel) = isc_mem_strdup(mctx,
+ destination->file.name);
+ FILE_STREAM(channel) = NULL;
+ FILE_VERSIONS(channel) = destination->file.versions;
+ FILE_SUFFIX(channel) = destination->file.suffix;
+ FILE_MAXSIZE(channel) = destination->file.maximum_size;
+ FILE_MAXREACHED(channel) = false;
+ break;
+
+ case ISC_LOG_TOFILEDESC:
+ FILE_NAME(channel) = NULL;
+ FILE_STREAM(channel) = destination->file.stream;
+ FILE_MAXSIZE(channel) = 0;
+ FILE_VERSIONS(channel) = ISC_LOG_ROLLNEVER;
+ FILE_SUFFIX(channel) = isc_log_rollsuffix_increment;
+ break;
+
+ case ISC_LOG_TONULL:
+ /* Nothing. */
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ ISC_LIST_PREPEND(lcfg->channels, channel, link);
+
+ /*
+ * If default_stderr was redefined, make the default category
+ * point to the new default_stderr.
+ */
+ if (strcmp(name, "default_stderr") == 0) {
+ default_channel.channel = channel;
+ }
+}
+
+isc_result_t
+isc_log_usechannel(isc_logconfig_t *lcfg, const char *name,
+ const isc_logcategory_t *category,
+ const isc_logmodule_t *module) {
+ isc_log_t *lctx;
+ isc_logchannel_t *channel;
+
+ REQUIRE(VALID_CONFIG(lcfg));
+ REQUIRE(name != NULL);
+
+ lctx = lcfg->lctx;
+
+ REQUIRE(category == NULL || category->id < lctx->category_count);
+ REQUIRE(module == NULL || module->id < lctx->module_count);
+
+ for (channel = ISC_LIST_HEAD(lcfg->channels); channel != NULL;
+ channel = ISC_LIST_NEXT(channel, link))
+ {
+ if (strcmp(name, channel->name) == 0) {
+ break;
+ }
+ }
+
+ if (channel == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (category != NULL) {
+ assignchannel(lcfg, category->id, module, channel);
+ } else {
+ /*
+ * Assign to all categories. Note that this includes
+ * the default channel.
+ */
+ for (size_t i = 0; i < lctx->category_count; i++) {
+ assignchannel(lcfg, i, module, channel);
+ }
+ }
+
+ /*
+ * Update the highest logging level, if the current lcfg is in use.
+ */
+ if (lcfg->lctx->logconfig == lcfg) {
+ sync_highest_level(lctx, lcfg);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_log_write(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format, ...) {
+ va_list args;
+
+ /*
+ * Contract checking is done in isc_log_doit().
+ */
+
+ va_start(args, format);
+ isc_log_doit(lctx, category, module, level, false, format, args);
+ va_end(args);
+}
+
+void
+isc_log_vwrite(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format,
+ va_list args) {
+ /*
+ * Contract checking is done in isc_log_doit().
+ */
+ isc_log_doit(lctx, category, module, level, false, format, args);
+}
+
+void
+isc_log_write1(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format, ...) {
+ va_list args;
+
+ /*
+ * Contract checking is done in isc_log_doit().
+ */
+
+ va_start(args, format);
+ isc_log_doit(lctx, category, module, level, true, format, args);
+ va_end(args);
+}
+
+void
+isc_log_vwrite1(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *format,
+ va_list args) {
+ /*
+ * Contract checking is done in isc_log_doit().
+ */
+ isc_log_doit(lctx, category, module, level, true, format, args);
+}
+
+void
+isc_log_setcontext(isc_log_t *lctx) {
+ isc_lctx = lctx;
+}
+
+void
+isc_log_setdebuglevel(isc_log_t *lctx, unsigned int level) {
+ REQUIRE(VALID_CONTEXT(lctx));
+
+ atomic_store_release(&lctx->debug_level, level);
+ /*
+ * Close ISC_LOG_DEBUGONLY channels if level is zero.
+ */
+ if (level == 0) {
+ RDLOCK(&lctx->lcfg_rwl);
+ isc_logconfig_t *lcfg = lctx->logconfig;
+ if (lcfg != NULL) {
+ LOCK(&lctx->lock);
+ for (isc_logchannel_t *channel =
+ ISC_LIST_HEAD(lcfg->channels);
+ channel != NULL;
+ channel = ISC_LIST_NEXT(channel, link))
+ {
+ if (channel->type == ISC_LOG_TOFILE &&
+ (channel->flags & ISC_LOG_DEBUGONLY) != 0 &&
+ FILE_STREAM(channel) != NULL)
+ {
+ (void)fclose(FILE_STREAM(channel));
+ FILE_STREAM(channel) = NULL;
+ }
+ }
+ UNLOCK(&lctx->lock);
+ }
+ RDUNLOCK(&lctx->lcfg_rwl);
+ }
+}
+
+unsigned int
+isc_log_getdebuglevel(isc_log_t *lctx) {
+ REQUIRE(VALID_CONTEXT(lctx));
+
+ return (atomic_load_acquire(&lctx->debug_level));
+}
+
+void
+isc_log_setduplicateinterval(isc_logconfig_t *lcfg, unsigned int interval) {
+ REQUIRE(VALID_CONFIG(lcfg));
+
+ lcfg->duplicate_interval = interval;
+}
+
+unsigned int
+isc_log_getduplicateinterval(isc_logconfig_t *lcfg) {
+ REQUIRE(VALID_CONTEXT(lcfg));
+
+ return (lcfg->duplicate_interval);
+}
+
+void
+isc_log_settag(isc_logconfig_t *lcfg, const char *tag) {
+ REQUIRE(VALID_CONFIG(lcfg));
+
+ if (tag != NULL && *tag != '\0') {
+ if (lcfg->tag != NULL) {
+ isc_mem_free(lcfg->lctx->mctx, lcfg->tag);
+ }
+ lcfg->tag = isc_mem_strdup(lcfg->lctx->mctx, tag);
+ } else {
+ if (lcfg->tag != NULL) {
+ isc_mem_free(lcfg->lctx->mctx, lcfg->tag);
+ }
+ lcfg->tag = NULL;
+ }
+}
+
+char *
+isc_log_gettag(isc_logconfig_t *lcfg) {
+ REQUIRE(VALID_CONFIG(lcfg));
+
+ return (lcfg->tag);
+}
+
+/* XXXDCL NT -- This interface will assuredly be changing. */
+void
+isc_log_opensyslog(const char *tag, int options, int facility) {
+ (void)openlog(tag, options, facility);
+}
+
+void
+isc_log_closefilelogs(isc_log_t *lctx) {
+ REQUIRE(VALID_CONTEXT(lctx));
+
+ RDLOCK(&lctx->lcfg_rwl);
+ isc_logconfig_t *lcfg = lctx->logconfig;
+ if (lcfg != NULL) {
+ LOCK(&lctx->lock);
+ for (isc_logchannel_t *channel = ISC_LIST_HEAD(lcfg->channels);
+ channel != NULL; channel = ISC_LIST_NEXT(channel, link))
+ {
+ if (channel->type == ISC_LOG_TOFILE &&
+ FILE_STREAM(channel) != NULL)
+ {
+ (void)fclose(FILE_STREAM(channel));
+ FILE_STREAM(channel) = NULL;
+ }
+ }
+ UNLOCK(&lctx->lock);
+ }
+ RDUNLOCK(&lctx->lcfg_rwl);
+}
+
+/****
+**** Internal functions
+****/
+
+static void
+assignchannel(isc_logconfig_t *lcfg, unsigned int category_id,
+ const isc_logmodule_t *module, isc_logchannel_t *channel) {
+ isc_logchannellist_t *new_item;
+ isc_log_t *lctx;
+
+ REQUIRE(VALID_CONFIG(lcfg));
+
+ lctx = lcfg->lctx;
+
+ REQUIRE(category_id < lctx->category_count);
+ REQUIRE(module == NULL || module->id < lctx->module_count);
+ REQUIRE(channel != NULL);
+
+ /*
+ * Ensure lcfg->channellist_count == lctx->category_count.
+ */
+ sync_channellist(lcfg);
+
+ new_item = isc_mem_get(lctx->mctx, sizeof(*new_item));
+
+ new_item->channel = channel;
+ new_item->module = module;
+ ISC_LIST_INITANDPREPEND(lcfg->channellists[category_id], new_item,
+ link);
+
+ /*
+ * Remember the highest logging level set by any channel in the
+ * logging config, so isc_log_doit() can quickly return if the
+ * message is too high to be logged by any channel.
+ */
+ if (channel->type != ISC_LOG_TONULL) {
+ if (lcfg->highest_level < channel->level) {
+ lcfg->highest_level = channel->level;
+ }
+ if (channel->level == ISC_LOG_DYNAMIC) {
+ lcfg->dynamic = true;
+ }
+ }
+}
+
+/*
+ * This would ideally be part of isc_log_registercategories(), except then
+ * that function would have to return isc_result_t instead of void.
+ */
+static void
+sync_channellist(isc_logconfig_t *lcfg) {
+ unsigned int bytes;
+ isc_log_t *lctx;
+ void *lists;
+
+ REQUIRE(VALID_CONFIG(lcfg));
+
+ lctx = lcfg->lctx;
+
+ REQUIRE(lctx->category_count != 0);
+
+ if (lctx->category_count == lcfg->channellist_count) {
+ return;
+ }
+
+ bytes = lctx->category_count * sizeof(ISC_LIST(isc_logchannellist_t));
+
+ lists = isc_mem_get(lctx->mctx, bytes);
+
+ memset(lists, 0, bytes);
+
+ if (lcfg->channellist_count != 0) {
+ bytes = lcfg->channellist_count *
+ sizeof(ISC_LIST(isc_logchannellist_t));
+ memmove(lists, lcfg->channellists, bytes);
+ isc_mem_put(lctx->mctx, lcfg->channellists, bytes);
+ }
+
+ lcfg->channellists = lists;
+ lcfg->channellist_count = lctx->category_count;
+}
+
+static void
+sync_highest_level(isc_log_t *lctx, isc_logconfig_t *lcfg) {
+ atomic_store(&lctx->highest_level, lcfg->highest_level);
+ atomic_store(&lctx->dynamic, lcfg->dynamic);
+}
+
+static isc_result_t
+greatest_version(isc_logfile_t *file, int versions, int *greatestp) {
+ char *digit_end;
+ char dirbuf[PATH_MAX + 1];
+ const char *bname;
+ const char *dirname = ".";
+ int version, greatest = -1;
+ isc_dir_t dir;
+ isc_result_t result;
+ size_t bnamelen;
+
+ bname = strrchr(file->name, '/');
+ if (bname != NULL) {
+ /*
+ * Copy the complete file name to dirbuf.
+ */
+ size_t len = strlcpy(dirbuf, file->name, sizeof(dirbuf));
+ if (len >= sizeof(dirbuf)) {
+ result = ISC_R_NOSPACE;
+ syslog(LOG_ERR, "unable to remove log files: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ /*
+ * Truncate after trailing '/' so the code works for
+ * files in the root directory.
+ */
+ bname++;
+ dirbuf[bname - file->name] = '\0';
+ dirname = dirbuf;
+ } else {
+ bname = file->name;
+ }
+ bnamelen = strlen(bname);
+
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, dirname);
+
+ /*
+ * Return if the directory open failed.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.length > bnamelen &&
+ strncmp(dir.entry.name, bname, bnamelen) == 0 &&
+ dir.entry.name[bnamelen] == '.')
+ {
+ version = strtol(&dir.entry.name[bnamelen + 1],
+ &digit_end, 10);
+ /*
+ * Remove any backup files that exceed versions.
+ */
+ if (*digit_end == '\0' && version >= versions) {
+ int n = unlinkat(dirfd(dir.handle),
+ dir.entry.name, 0);
+ if (n < 0) {
+ result = isc_errno_toresult(errno);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ syslog(LOG_ERR,
+ "unable to remove log "
+ "file '%s%s': %s",
+ bname == file->name
+ ? ""
+ : dirname,
+ dir.entry.name,
+ isc_result_totext(
+ result));
+ }
+ }
+ } else if (*digit_end == '\0' && version > greatest) {
+ greatest = version;
+ }
+ }
+ }
+ isc_dir_close(&dir);
+
+ *greatestp = greatest;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+insert_sort(int64_t to_keep[], int64_t versions, int64_t version) {
+ int i = 0;
+ while (i < versions && version < to_keep[i]) {
+ i++;
+ }
+ if (i == versions) {
+ return;
+ }
+ if (i < versions - 1) {
+ memmove(&to_keep[i + 1], &to_keep[i],
+ sizeof(to_keep[0]) * (versions - i - 1));
+ }
+ to_keep[i] = version;
+}
+
+static int64_t
+last_to_keep(int64_t versions, isc_dir_t *dirp, const char *bname,
+ size_t bnamelen) {
+ int64_t to_keep[ISC_LOG_MAX_VERSIONS] = { 0 };
+ int64_t version = 0;
+
+ if (versions <= 0) {
+ return (INT64_MAX);
+ }
+
+ if (versions > ISC_LOG_MAX_VERSIONS) {
+ versions = ISC_LOG_MAX_VERSIONS;
+ }
+ /*
+ * First we fill 'to_keep' structure using insertion sort
+ */
+ memset(to_keep, 0, sizeof(to_keep));
+ while (isc_dir_read(dirp) == ISC_R_SUCCESS) {
+ char *digit_end = NULL;
+ char *ename = NULL;
+
+ if (dirp->entry.length <= bnamelen ||
+ strncmp(dirp->entry.name, bname, bnamelen) != 0 ||
+ dirp->entry.name[bnamelen] != '.')
+ {
+ continue;
+ }
+
+ ename = &dirp->entry.name[bnamelen + 1];
+ version = strtoull(ename, &digit_end, 10);
+ if (*digit_end == '\0') {
+ insert_sort(to_keep, versions, version);
+ }
+ }
+
+ isc_dir_reset(dirp);
+
+ /*
+ * to_keep[versions - 1] is the last one we want to keep
+ */
+ return (to_keep[versions - 1]);
+}
+
+static isc_result_t
+remove_old_tsversions(isc_logfile_t *file, int versions) {
+ char *digit_end;
+ char dirbuf[PATH_MAX + 1];
+ const char *bname;
+ const char *dirname = ".";
+ int64_t version, last = INT64_MAX;
+ isc_dir_t dir;
+ isc_result_t result;
+ size_t bnamelen;
+
+ bname = strrchr(file->name, '/');
+ if (bname != NULL) {
+ /*
+ * Copy the complete file name to dirbuf.
+ */
+ size_t len = strlcpy(dirbuf, file->name, sizeof(dirbuf));
+ if (len >= sizeof(dirbuf)) {
+ result = ISC_R_NOSPACE;
+ syslog(LOG_ERR, "unable to remove log files: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ /*
+ * Truncate after trailing '/' so the code works for
+ * files in the root directory.
+ */
+ bname++;
+ dirbuf[bname - file->name] = '\0';
+ dirname = dirbuf;
+ } else {
+ bname = file->name;
+ }
+ bnamelen = strlen(bname);
+
+ isc_dir_init(&dir);
+ result = isc_dir_open(&dir, dirname);
+
+ /*
+ * Return if the directory open failed.
+ */
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ last = last_to_keep(versions, &dir, bname, bnamelen);
+
+ while (isc_dir_read(&dir) == ISC_R_SUCCESS) {
+ if (dir.entry.length > bnamelen &&
+ strncmp(dir.entry.name, bname, bnamelen) == 0 &&
+ dir.entry.name[bnamelen] == '.')
+ {
+ version = strtoull(&dir.entry.name[bnamelen + 1],
+ &digit_end, 10);
+ /*
+ * Remove any backup files that exceed versions.
+ */
+ if (*digit_end == '\0' && version < last) {
+ int n = unlinkat(dirfd(dir.handle),
+ dir.entry.name, 0);
+ if (n < 0) {
+ result = isc_errno_toresult(errno);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_FILENOTFOUND)
+ {
+ syslog(LOG_ERR,
+ "unable to remove log "
+ "file '%s%s': %s",
+ bname == file->name
+ ? ""
+ : dirname,
+ dir.entry.name,
+ isc_result_totext(
+ result));
+ }
+ }
+ }
+ }
+ }
+ isc_dir_close(&dir);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+roll_increment(isc_logfile_t *file) {
+ int i, n, greatest;
+ char current[PATH_MAX + 1];
+ char newpath[PATH_MAX + 1];
+ const char *path;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(file->versions != 0);
+
+ path = file->name;
+
+ if (file->versions == ISC_LOG_ROLLINFINITE) {
+ /*
+ * Find the first missing entry in the log file sequence.
+ */
+ for (greatest = 0; greatest < INT_MAX; greatest++) {
+ n = snprintf(current, sizeof(current), "%s.%u", path,
+ (unsigned)greatest);
+ if (n >= (int)sizeof(current) || n < 0 ||
+ !isc_file_exists(current))
+ {
+ break;
+ }
+ }
+ } else {
+ /*
+ * Get the largest existing version and remove any
+ * version greater than the permitted version.
+ */
+ result = greatest_version(file, file->versions, &greatest);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Increment if greatest is not the actual maximum value.
+ */
+ if (greatest < file->versions - 1) {
+ greatest++;
+ }
+ }
+
+ for (i = greatest; i > 0; i--) {
+ result = ISC_R_SUCCESS;
+ n = snprintf(current, sizeof(current), "%s.%u", path,
+ (unsigned)(i - 1));
+ if (n >= (int)sizeof(current) || n < 0) {
+ result = ISC_R_NOSPACE;
+ }
+ if (result == ISC_R_SUCCESS) {
+ n = snprintf(newpath, sizeof(newpath), "%s.%u", path,
+ (unsigned)i);
+ if (n >= (int)sizeof(newpath) || n < 0) {
+ result = ISC_R_NOSPACE;
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ result = isc_file_rename(current, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR,
+ "unable to rename log file '%s.%u' to "
+ "'%s.%u': %s",
+ path, i - 1, path, i, isc_result_totext(result));
+ }
+ }
+
+ n = snprintf(newpath, sizeof(newpath), "%s.0", path);
+ if (n >= (int)sizeof(newpath) || n < 0) {
+ result = ISC_R_NOSPACE;
+ } else {
+ result = isc_file_rename(path, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR, "unable to rename log file '%s' to '%s.0': %s",
+ path, path, isc_result_totext(result));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+roll_timestamp(isc_logfile_t *file) {
+ int n;
+ char newts[PATH_MAX + 1];
+ char newpath[PATH_MAX + 1];
+ const char *path;
+ isc_time_t now;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(file != NULL);
+ REQUIRE(file->versions != 0);
+
+ path = file->name;
+
+ /*
+ * First find all the logfiles and remove the oldest ones
+ * Save one fewer than file->versions because we'll be renaming
+ * the existing file to a timestamped version after this.
+ */
+ if (file->versions != ISC_LOG_ROLLINFINITE) {
+ remove_old_tsversions(file, file->versions - 1);
+ }
+
+ /* Then just rename the current logfile */
+ isc_time_now(&now);
+ isc_time_formatshorttimestamp(&now, newts, PATH_MAX + 1);
+ n = snprintf(newpath, sizeof(newpath), "%s.%s", path, newts);
+ if (n >= (int)sizeof(newpath) || n < 0) {
+ result = ISC_R_NOSPACE;
+ } else {
+ result = isc_file_rename(path, newpath);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR, "unable to rename log file '%s' to '%s.0': %s",
+ path, path, isc_result_totext(result));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_logfile_roll(isc_logfile_t *file) {
+ isc_result_t result;
+
+ REQUIRE(file != NULL);
+
+ /*
+ * Do nothing (not even excess version trimming) if ISC_LOG_ROLLNEVER
+ * is specified. Apparently complete external control over the log
+ * files is desired.
+ */
+ if (file->versions == ISC_LOG_ROLLNEVER) {
+ return (ISC_R_SUCCESS);
+ } else if (file->versions == 0) {
+ result = isc_file_remove(file->name);
+ if (result != ISC_R_SUCCESS && result != ISC_R_FILENOTFOUND) {
+ syslog(LOG_ERR, "unable to remove log file '%s': %s",
+ file->name, isc_result_totext(result));
+ }
+ return (ISC_R_SUCCESS);
+ }
+
+ switch (file->suffix) {
+ case isc_log_rollsuffix_increment:
+ return (roll_increment(file));
+ case isc_log_rollsuffix_timestamp:
+ return (roll_timestamp(file));
+ default:
+ return (ISC_R_UNEXPECTED);
+ }
+}
+
+static isc_result_t
+isc_log_open(isc_logchannel_t *channel) {
+ struct stat statbuf;
+ bool regular_file;
+ bool roll = false;
+ isc_result_t result = ISC_R_SUCCESS;
+ const char *path;
+
+ REQUIRE(channel->type == ISC_LOG_TOFILE);
+ REQUIRE(FILE_STREAM(channel) == NULL);
+
+ path = FILE_NAME(channel);
+
+ REQUIRE(path != NULL && *path != '\0');
+
+ /*
+ * Determine type of file; only regular files will be
+ * version renamed, and only if the base file exists
+ * and either has no size limit or has reached its size limit.
+ */
+ if (stat(path, &statbuf) == 0) {
+ regular_file = S_ISREG(statbuf.st_mode) ? true : false;
+ /* XXXDCL if not regular_file complain? */
+ if ((FILE_MAXSIZE(channel) == 0 &&
+ FILE_VERSIONS(channel) != ISC_LOG_ROLLNEVER) ||
+ (FILE_MAXSIZE(channel) > 0 &&
+ statbuf.st_size >= FILE_MAXSIZE(channel)))
+ {
+ roll = regular_file;
+ }
+ } else if (errno == ENOENT) {
+ regular_file = true;
+ POST(regular_file);
+ } else {
+ result = ISC_R_INVALIDFILE;
+ }
+
+ /*
+ * Version control.
+ */
+ if (result == ISC_R_SUCCESS && roll) {
+ if (FILE_VERSIONS(channel) == ISC_LOG_ROLLNEVER) {
+ return (ISC_R_MAXSIZE);
+ }
+ result = isc_logfile_roll(&channel->destination.file);
+ if (result != ISC_R_SUCCESS) {
+ if ((channel->flags & ISC_LOG_OPENERR) == 0) {
+ syslog(LOG_ERR,
+ "isc_log_open: isc_logfile_roll '%s' "
+ "failed: %s",
+ FILE_NAME(channel),
+ isc_result_totext(result));
+ channel->flags |= ISC_LOG_OPENERR;
+ }
+ return (result);
+ }
+ }
+
+ result = isc_stdio_open(path, "a", &FILE_STREAM(channel));
+
+ return (result);
+}
+
+ISC_NO_SANITIZE_THREAD bool
+isc_log_wouldlog(isc_log_t *lctx, int level) {
+ /*
+ * Try to avoid locking the mutex for messages which can't
+ * possibly be logged to any channels -- primarily debugging
+ * messages that the debug level is not high enough to print.
+ *
+ * If the level is (mathematically) less than or equal to the
+ * highest_level, or if there is a dynamic channel and the level is
+ * less than or equal to the debug level, the main loop must be
+ * entered to see if the message should really be output.
+ */
+ if (lctx == NULL) {
+ return (false);
+ }
+ if (forcelog) {
+ return (true);
+ }
+
+ int highest_level = atomic_load_acquire(&lctx->highest_level);
+ if (level <= highest_level) {
+ return (true);
+ }
+ if (atomic_load_acquire(&lctx->dynamic)) {
+ int debug_level = atomic_load_acquire(&lctx->debug_level);
+ if (level <= debug_level) {
+ return (true);
+ }
+ }
+
+ return (false);
+}
+
+static void
+isc_log_doit(isc_log_t *lctx, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, bool write_once,
+ const char *format, va_list args) {
+ int syslog_level;
+ const char *time_string;
+ char local_time[64];
+ char iso8601z_string[64];
+ char iso8601l_string[64];
+ char level_string[24] = { 0 };
+ struct stat statbuf;
+ bool matched = false;
+ bool printtime, iso8601, utc, printtag, printcolon;
+ bool printcategory, printmodule, printlevel, buffered;
+ isc_logchannel_t *channel;
+ isc_logchannellist_t *category_channels;
+ int_fast32_t dlevel;
+ isc_result_t result;
+
+ REQUIRE(lctx == NULL || VALID_CONTEXT(lctx));
+ REQUIRE(category != NULL);
+ REQUIRE(module != NULL);
+ REQUIRE(level != ISC_LOG_DYNAMIC);
+ REQUIRE(format != NULL);
+
+ /*
+ * Programs can use libraries that use this logging code without
+ * wanting to do any logging, thus the log context is allowed to
+ * be non-existent.
+ */
+ if (lctx == NULL) {
+ return;
+ }
+
+ REQUIRE(category->id < lctx->category_count);
+ REQUIRE(module->id < lctx->module_count);
+
+ if (!isc_log_wouldlog(lctx, level)) {
+ return;
+ }
+
+ local_time[0] = '\0';
+ iso8601l_string[0] = '\0';
+ iso8601z_string[0] = '\0';
+
+ RDLOCK(&lctx->lcfg_rwl);
+ LOCK(&lctx->lock);
+
+ lctx->buffer[0] = '\0';
+
+ isc_logconfig_t *lcfg = lctx->logconfig;
+
+ category_channels = ISC_LIST_HEAD(lcfg->channellists[category->id]);
+
+ /*
+ * XXXDCL add duplicate filtering? (To not write multiple times
+ * to the same source via various channels).
+ */
+ do {
+ /*
+ * If the channel list end was reached and a match was
+ * made, everything is finished.
+ */
+ if (category_channels == NULL && matched) {
+ break;
+ }
+
+ if (category_channels == NULL && !matched &&
+ category_channels != ISC_LIST_HEAD(lcfg->channellists[0]))
+ {
+ /*
+ * No category/module pair was explicitly
+ * configured. Try the category named "default".
+ */
+ category_channels =
+ ISC_LIST_HEAD(lcfg->channellists[0]);
+ }
+
+ if (category_channels == NULL && !matched) {
+ /*
+ * No matching module was explicitly configured
+ * for the category named "default". Use the
+ * internal default channel.
+ */
+ category_channels = &default_channel;
+ }
+
+ if (category_channels->module != NULL &&
+ category_channels->module != module)
+ {
+ category_channels = ISC_LIST_NEXT(category_channels,
+ link);
+ continue;
+ }
+
+ matched = true;
+
+ channel = category_channels->channel;
+ category_channels = ISC_LIST_NEXT(category_channels, link);
+
+ if (!forcelog) {
+ dlevel = atomic_load_acquire(&lctx->debug_level);
+ if (((channel->flags & ISC_LOG_DEBUGONLY) != 0) &&
+ dlevel == 0)
+ {
+ continue;
+ }
+
+ if (channel->level == ISC_LOG_DYNAMIC) {
+ if (dlevel < level) {
+ continue;
+ }
+ } else if (channel->level < level) {
+ continue;
+ }
+ }
+
+ if ((channel->flags & ISC_LOG_PRINTTIME) != 0 &&
+ local_time[0] == '\0')
+ {
+ isc_time_t isctime;
+
+ TIME_NOW(&isctime);
+
+ isc_time_formattimestamp(&isctime, local_time,
+ sizeof(local_time));
+ isc_time_formatISO8601ms(&isctime, iso8601z_string,
+ sizeof(iso8601z_string));
+ isc_time_formatISO8601Lms(&isctime, iso8601l_string,
+ sizeof(iso8601l_string));
+ }
+
+ if ((channel->flags & ISC_LOG_PRINTLEVEL) != 0 &&
+ level_string[0] == '\0')
+ {
+ if (level < ISC_LOG_CRITICAL) {
+ snprintf(level_string, sizeof(level_string),
+ "level %d: ", level);
+ } else if (level > ISC_LOG_DYNAMIC) {
+ snprintf(level_string, sizeof(level_string),
+ "%s %d: ", log_level_strings[0],
+ level);
+ } else {
+ snprintf(level_string, sizeof(level_string),
+ "%s: ", log_level_strings[-level]);
+ }
+ }
+
+ /*
+ * Only format the message once.
+ */
+ if (lctx->buffer[0] == '\0') {
+ (void)vsnprintf(lctx->buffer, sizeof(lctx->buffer),
+ format, args);
+
+ /*
+ * Check for duplicates.
+ */
+ if (write_once) {
+ isc_logmessage_t *message, *next;
+ isc_time_t oldest;
+ isc_interval_t interval;
+ size_t size;
+
+ isc_interval_set(&interval,
+ lcfg->duplicate_interval, 0);
+
+ /*
+ * 'oldest' is the age of the oldest
+ * messages which fall within the
+ * duplicate_interval range.
+ */
+ TIME_NOW(&oldest);
+ if (isc_time_subtract(&oldest, &interval,
+ &oldest) != ISC_R_SUCCESS)
+ {
+ /*
+ * Can't effectively do the
+ * checking without having a
+ * valid time.
+ */
+ message = NULL;
+ } else {
+ message = ISC_LIST_HEAD(lctx->messages);
+ }
+
+ while (message != NULL) {
+ if (isc_time_compare(&message->time,
+ &oldest) < 0)
+ {
+ /*
+ * This message is older
+ * than the
+ * duplicate_interval,
+ * so it should be
+ * dropped from the
+ * history.
+ *
+ * Setting the interval
+ * to be to be longer
+ * will obviously not
+ * cause the expired
+ * message to spring
+ * back into existence.
+ */
+ next = ISC_LIST_NEXT(message,
+ link);
+
+ ISC_LIST_UNLINK(lctx->messages,
+ message, link);
+
+ isc_mem_put(
+ lctx->mctx, message,
+ sizeof(*message) + 1 +
+ strlen(message->text));
+
+ message = next;
+ continue;
+ }
+
+ /*
+ * This message is in the
+ * duplicate filtering interval
+ * ...
+ */
+ if (strcmp(lctx->buffer,
+ message->text) == 0)
+ {
+ /*
+ * ... and it is a
+ * duplicate. Unlock the
+ * mutex and get the
+ * hell out of Dodge.
+ */
+ goto unlock;
+ }
+
+ message = ISC_LIST_NEXT(message, link);
+ }
+
+ /*
+ * It wasn't in the duplicate interval,
+ * so add it to the message list.
+ */
+ size = sizeof(isc_logmessage_t) +
+ strlen(lctx->buffer) + 1;
+ message = isc_mem_get(lctx->mctx, size);
+ message->text = (char *)(message + 1);
+ size -= sizeof(isc_logmessage_t);
+ strlcpy(message->text, lctx->buffer, size);
+ TIME_NOW(&message->time);
+ ISC_LINK_INIT(message, link);
+ ISC_LIST_APPEND(lctx->messages, message, link);
+ }
+ }
+
+ utc = ((channel->flags & ISC_LOG_UTC) != 0);
+ iso8601 = ((channel->flags & ISC_LOG_ISO8601) != 0);
+ printtime = ((channel->flags & ISC_LOG_PRINTTIME) != 0);
+ printtag = ((channel->flags &
+ (ISC_LOG_PRINTTAG | ISC_LOG_PRINTPREFIX)) != 0 &&
+ lcfg->tag != NULL);
+ printcolon = ((channel->flags & ISC_LOG_PRINTTAG) != 0 &&
+ lcfg->tag != NULL);
+ printcategory = ((channel->flags & ISC_LOG_PRINTCATEGORY) != 0);
+ printmodule = ((channel->flags & ISC_LOG_PRINTMODULE) != 0);
+ printlevel = ((channel->flags & ISC_LOG_PRINTLEVEL) != 0);
+ buffered = ((channel->flags & ISC_LOG_BUFFERED) != 0);
+
+ if (printtime) {
+ if (iso8601) {
+ if (utc) {
+ time_string = iso8601z_string;
+ } else {
+ time_string = iso8601l_string;
+ }
+ } else {
+ time_string = local_time;
+ }
+ } else {
+ time_string = "";
+ }
+
+ switch (channel->type) {
+ case ISC_LOG_TOFILE:
+ if (FILE_MAXREACHED(channel)) {
+ /*
+ * If the file can be rolled, OR
+ * If the file no longer exists, OR
+ * If the file is less than the maximum
+ * size, (such as if it had been renamed
+ * and a new one touched, or it was
+ * truncated in place)
+ * ... then close it to trigger
+ * reopening.
+ */
+ if (FILE_VERSIONS(channel) !=
+ ISC_LOG_ROLLNEVER ||
+ (stat(FILE_NAME(channel), &statbuf) != 0 &&
+ errno == ENOENT) ||
+ statbuf.st_size < FILE_MAXSIZE(channel))
+ {
+ (void)fclose(FILE_STREAM(channel));
+ FILE_STREAM(channel) = NULL;
+ FILE_MAXREACHED(channel) = false;
+ } else {
+ /*
+ * Eh, skip it.
+ */
+ break;
+ }
+ }
+
+ if (FILE_STREAM(channel) == NULL) {
+ result = isc_log_open(channel);
+ if (result != ISC_R_SUCCESS &&
+ result != ISC_R_MAXSIZE &&
+ (channel->flags & ISC_LOG_OPENERR) == 0)
+ {
+ syslog(LOG_ERR,
+ "isc_log_open '%s' "
+ "failed: %s",
+ FILE_NAME(channel),
+ isc_result_totext(result));
+ channel->flags |= ISC_LOG_OPENERR;
+ }
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ channel->flags &= ~ISC_LOG_OPENERR;
+ }
+ FALLTHROUGH;
+
+ case ISC_LOG_TOFILEDESC:
+ fprintf(FILE_STREAM(channel), "%s%s%s%s%s%s%s%s%s%s\n",
+ printtime ? time_string : "",
+ printtime ? " " : "", printtag ? lcfg->tag : "",
+ printcolon ? ": " : "",
+ printcategory ? category->name : "",
+ printcategory ? ": " : "",
+ printmodule ? (module != NULL ? module->name
+ : "no_module")
+ : "",
+ printmodule ? ": " : "",
+ printlevel ? level_string : "", lctx->buffer);
+
+ if (!buffered) {
+ fflush(FILE_STREAM(channel));
+ }
+
+ /*
+ * If the file now exceeds its maximum size
+ * threshold, note it so that it will not be
+ * logged to any more.
+ */
+ if (FILE_MAXSIZE(channel) > 0) {
+ INSIST(channel->type == ISC_LOG_TOFILE);
+
+ /* XXXDCL NT fstat/fileno */
+ /* XXXDCL complain if fstat fails? */
+ if (fstat(fileno(FILE_STREAM(channel)),
+ &statbuf) >= 0 &&
+ statbuf.st_size > FILE_MAXSIZE(channel))
+ {
+ FILE_MAXREACHED(channel) = true;
+ }
+ }
+
+ break;
+
+ case ISC_LOG_TOSYSLOG:
+ if (level > 0) {
+ syslog_level = LOG_DEBUG;
+ } else if (level < ISC_LOG_CRITICAL) {
+ syslog_level = LOG_CRIT;
+ } else {
+ syslog_level = syslog_map[-level];
+ }
+
+ (void)syslog(
+ FACILITY(channel) | syslog_level,
+ "%s%s%s%s%s%s%s%s%s%s",
+ printtime ? time_string : "",
+ printtime ? " " : "", printtag ? lcfg->tag : "",
+ printcolon ? ": " : "",
+ printcategory ? category->name : "",
+ printcategory ? ": " : "",
+ printmodule ? (module != NULL ? module->name
+ : "no_module")
+ : "",
+ printmodule ? ": " : "",
+ printlevel ? level_string : "", lctx->buffer);
+ break;
+
+ case ISC_LOG_TONULL:
+ break;
+ }
+ } while (1);
+
+unlock:
+ UNLOCK(&lctx->lock);
+ RDUNLOCK(&lctx->lcfg_rwl);
+}
+
+void
+isc_log_setforcelog(bool v) {
+ forcelog = v;
+}
diff --git a/lib/isc/managers.c b/lib/isc/managers.c
new file mode 100644
index 0000000..5e9a6c3
--- /dev/null
+++ b/lib/isc/managers.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/managers.h>
+#include <isc/util.h>
+
+#include "netmgr_p.h"
+#include "task_p.h"
+#include "timer_p.h"
+
+isc_result_t
+isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum,
+ isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
+ isc_timermgr_t **timermgrp) {
+ isc_result_t result;
+ isc_nm_t *netmgr = NULL;
+ isc_taskmgr_t *taskmgr = NULL;
+ isc_timermgr_t *timermgr = NULL;
+
+ REQUIRE(netmgrp != NULL && *netmgrp == NULL);
+ isc__netmgr_create(mctx, workers, &netmgr);
+ *netmgrp = netmgr;
+ INSIST(netmgr != NULL);
+
+ REQUIRE(taskmgrp == NULL || *taskmgrp == NULL);
+ if (taskmgrp != NULL) {
+ INSIST(netmgr != NULL);
+ result = isc__taskmgr_create(mctx, quantum, netmgr, &taskmgr);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_taskmgr_create() failed: %s",
+ isc_result_totext(result));
+ goto fail;
+ }
+ *taskmgrp = taskmgr;
+ }
+
+ REQUIRE(timermgrp == NULL || *timermgrp == NULL);
+ if (timermgrp != NULL) {
+ result = isc__timermgr_create(mctx, &timermgr);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR("isc_timermgr_create() failed: %s",
+ isc_result_totext(result));
+ goto fail;
+ }
+ *timermgrp = timermgr;
+ }
+
+ return (ISC_R_SUCCESS);
+fail:
+ isc_managers_destroy(netmgrp, taskmgrp, timermgrp);
+
+ return (result);
+}
+
+void
+isc_managers_destroy(isc_nm_t **netmgrp, isc_taskmgr_t **taskmgrp,
+ isc_timermgr_t **timermgrp) {
+ /*
+ * If we have a taskmgr to clean up, then we must also have a netmgr.
+ */
+ REQUIRE(taskmgrp == NULL || netmgrp != NULL);
+
+ /*
+ * The sequence of operations here is important:
+ *
+ * 1. Initiate shutdown of the taskmgr, sending shutdown events to
+ * all tasks that are not already shutting down.
+ */
+ if (taskmgrp != NULL) {
+ INSIST(*taskmgrp != NULL);
+ isc__taskmgr_shutdown(*taskmgrp);
+ }
+
+ /*
+ * 2. Initiate shutdown of the network manager, freeing clients
+ * and other resources and preventing new connections, but do
+ * not stop processing of existing events.
+ */
+ if (netmgrp != NULL) {
+ INSIST(*netmgrp != NULL);
+ isc__netmgr_shutdown(*netmgrp);
+ }
+
+ /*
+ * 3. Finish destruction of the task manager when all tasks
+ * have completed.
+ */
+ if (taskmgrp != NULL) {
+ isc__taskmgr_destroy(taskmgrp);
+ }
+
+ /*
+ * 4. Finish destruction of the netmgr, and wait until all
+ * references have been released.
+ */
+ if (netmgrp != NULL) {
+ isc__netmgr_destroy(netmgrp);
+ }
+
+ /*
+ * 5. Clean up the remaining managers.
+ */
+ if (timermgrp != NULL) {
+ INSIST(*timermgrp != NULL);
+ isc__timermgr_destroy(timermgrp);
+ }
+}
diff --git a/lib/isc/md.c b/lib/isc/md.c
new file mode 100644
index 0000000..a8c6312
--- /dev/null
+++ b/lib/isc/md.c
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <stdio.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+
+#include <isc/md.h>
+#include <isc/util.h>
+
+#include "openssl_shim.h"
+
+isc_md_t *
+isc_md_new(void) {
+ isc_md_t *md = EVP_MD_CTX_new();
+ RUNTIME_CHECK(md != NULL);
+ return (md);
+}
+
+void
+isc_md_free(isc_md_t *md) {
+ if (md == NULL) {
+ return;
+ }
+
+ EVP_MD_CTX_free(md);
+}
+
+isc_result_t
+isc_md_init(isc_md_t *md, const isc_md_type_t *md_type) {
+ REQUIRE(md != NULL);
+
+ if (md_type == NULL) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ if (EVP_DigestInit_ex(md, md_type, NULL) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_md_reset(isc_md_t *md) {
+ REQUIRE(md != NULL);
+
+ if (EVP_MD_CTX_reset(md) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_md_update(isc_md_t *md, const unsigned char *buf, const size_t len) {
+ REQUIRE(md != NULL);
+
+ if (buf == NULL || len == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (EVP_DigestUpdate(md, buf, len) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_md_final(isc_md_t *md, unsigned char *digest, unsigned int *digestlen) {
+ REQUIRE(md != NULL);
+ REQUIRE(digest != NULL);
+
+ if (EVP_DigestFinal_ex(md, digest, digestlen) != 1) {
+ ERR_clear_error();
+ return (ISC_R_CRYPTOFAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+const isc_md_type_t *
+isc_md_get_md_type(isc_md_t *md) {
+ REQUIRE(md != NULL);
+
+ return (EVP_MD_CTX_get0_md(md));
+}
+
+size_t
+isc_md_get_size(isc_md_t *md) {
+ REQUIRE(md != NULL);
+
+ return (EVP_MD_CTX_size(md));
+}
+
+size_t
+isc_md_get_block_size(isc_md_t *md) {
+ REQUIRE(md != NULL);
+
+ return (EVP_MD_CTX_block_size(md));
+}
+
+size_t
+isc_md_type_get_size(const isc_md_type_t *md_type) {
+ STATIC_ASSERT(ISC_MAX_MD_SIZE >= EVP_MAX_MD_SIZE,
+ "Change ISC_MAX_MD_SIZE to be greater than or equal to "
+ "EVP_MAX_MD_SIZE");
+ if (md_type != NULL) {
+ return ((size_t)EVP_MD_size(md_type));
+ }
+
+ return (ISC_MAX_MD_SIZE);
+}
+
+size_t
+isc_md_type_get_block_size(const isc_md_type_t *md_type) {
+ STATIC_ASSERT(ISC_MAX_MD_SIZE >= EVP_MAX_MD_SIZE,
+ "Change ISC_MAX_MD_SIZE to be greater than or equal to "
+ "EVP_MAX_MD_SIZE");
+ if (md_type != NULL) {
+ return ((size_t)EVP_MD_block_size(md_type));
+ }
+
+ return (ISC_MAX_MD_SIZE);
+}
+
+isc_result_t
+isc_md(const isc_md_type_t *md_type, const unsigned char *buf, const size_t len,
+ unsigned char *digest, unsigned int *digestlen) {
+ isc_md_t *md;
+ isc_result_t res;
+
+ md = isc_md_new();
+
+ res = isc_md_init(md, md_type);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ res = isc_md_update(md, buf, len);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+
+ res = isc_md_final(md, digest, digestlen);
+ if (res != ISC_R_SUCCESS) {
+ goto end;
+ }
+end:
+ isc_md_free(md);
+
+ return (res);
+}
+
+#define md_register_algorithm(alg) \
+ const isc_md_type_t *isc__md_##alg(void) { \
+ const isc_md_type_t *value = EVP_##alg(); \
+ if (value == NULL) { \
+ ERR_clear_error(); \
+ } \
+ return (value); \
+ }
+
+md_register_algorithm(md5);
+md_register_algorithm(sha1);
+md_register_algorithm(sha224);
+md_register_algorithm(sha256);
+md_register_algorithm(sha384);
+md_register_algorithm(sha512);
diff --git a/lib/isc/mem.c b/lib/isc/mem.c
new file mode 100644
index 0000000..61a66f6
--- /dev/null
+++ b/lib/isc/mem.c
@@ -0,0 +1,1908 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <isc/align.h>
+#include <isc/hash.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#ifdef HAVE_LIBXML2
+#include <libxml/xmlwriter.h>
+#define ISC_XMLCHAR (const xmlChar *)
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#include <json_object.h>
+#endif /* HAVE_JSON_C */
+
+/* On DragonFly BSD the header does not provide jemalloc API */
+#if defined(HAVE_MALLOC_NP_H) && !defined(__DragonFly__)
+#include <malloc_np.h>
+#define JEMALLOC_API_SUPPORTED 1
+#elif defined(HAVE_JEMALLOC)
+#include <jemalloc/jemalloc.h>
+#define JEMALLOC_API_SUPPORTED 1
+
+#if JEMALLOC_VERSION_MAJOR < 4
+#define sdallocx(ptr, size, flags) dallocx(ptr, flags)
+#define MALLOCX_TCACHE_NONE (0)
+#endif /* JEMALLOC_VERSION_MAJOR < 4 */
+
+#else
+#include "jemalloc_shim.h"
+#endif
+
+#include "mem_p.h"
+
+#define MCTXLOCK(m) LOCK(&m->lock)
+#define MCTXUNLOCK(m) UNLOCK(&m->lock)
+
+#ifndef ISC_MEM_DEBUGGING
+#define ISC_MEM_DEBUGGING 0
+#endif /* ifndef ISC_MEM_DEBUGGING */
+unsigned int isc_mem_debugging = ISC_MEM_DEBUGGING;
+unsigned int isc_mem_defaultflags = ISC_MEMFLAG_DEFAULT;
+
+#define ISC_MEM_ILLEGAL_ARENA (UINT_MAX)
+
+/*
+ * Constants.
+ */
+
+#define ZERO_ALLOCATION_SIZE sizeof(void *)
+#define ALIGNMENT 8U /*%< must be a power of 2 */
+#define ALIGNMENT_SIZE sizeof(size_info)
+#define DEBUG_TABLE_COUNT 512U
+#define STATS_BUCKETS 512U
+#define STATS_BUCKET_SIZE 32U
+
+/*
+ * Types.
+ */
+#if ISC_MEM_TRACKLINES
+typedef struct debuglink debuglink_t;
+struct debuglink {
+ ISC_LINK(debuglink_t) link;
+ const void *ptr;
+ size_t size;
+ const char *file;
+ unsigned int line;
+};
+
+typedef ISC_LIST(debuglink_t) debuglist_t;
+
+#define FLARG_PASS , file, line
+#define FLARG , const char *file, unsigned int line
+#else /* if ISC_MEM_TRACKLINES */
+#define FLARG_PASS
+#define FLARG
+#endif /* if ISC_MEM_TRACKLINES */
+
+typedef struct element element;
+struct element {
+ element *next;
+};
+
+struct stats {
+ atomic_size_t gets;
+ atomic_size_t totalgets;
+};
+
+#define MEM_MAGIC ISC_MAGIC('M', 'e', 'm', 'C')
+#define VALID_CONTEXT(c) ISC_MAGIC_VALID(c, MEM_MAGIC)
+
+/* List of all active memory contexts. */
+
+static ISC_LIST(isc_mem_t) contexts;
+
+static isc_once_t init_once = ISC_ONCE_INIT;
+static isc_once_t shut_once = ISC_ONCE_INIT;
+static isc_mutex_t contextslock;
+
+/*%
+ * Total size of lost memory due to a bug of external library.
+ * Locked by the global lock.
+ */
+static uint64_t totallost;
+
+struct isc_mem {
+ unsigned int magic;
+ unsigned int flags;
+ unsigned int jemalloc_flags;
+ unsigned int jemalloc_arena;
+ isc_mutex_t lock;
+ bool checkfree;
+ struct stats stats[STATS_BUCKETS + 1];
+ isc_refcount_t references;
+ char name[16];
+ atomic_size_t total;
+ atomic_size_t inuse;
+ atomic_size_t maxinuse;
+ atomic_size_t malloced;
+ atomic_size_t maxmalloced;
+ atomic_bool hi_called;
+ atomic_bool is_overmem;
+ isc_mem_water_t water;
+ void *water_arg;
+ atomic_size_t hi_water;
+ atomic_size_t lo_water;
+ ISC_LIST(isc_mempool_t) pools;
+ unsigned int poolcnt;
+
+#if ISC_MEM_TRACKLINES
+ debuglist_t *debuglist;
+ size_t debuglistcnt;
+#endif /* if ISC_MEM_TRACKLINES */
+
+ ISC_LINK(isc_mem_t) link;
+};
+
+#define MEMPOOL_MAGIC ISC_MAGIC('M', 'E', 'M', 'p')
+#define VALID_MEMPOOL(c) ISC_MAGIC_VALID(c, MEMPOOL_MAGIC)
+
+struct isc_mempool {
+ /* always unlocked */
+ unsigned int magic;
+ isc_mem_t *mctx; /*%< our memory context */
+ ISC_LINK(isc_mempool_t) link; /*%< next pool in this mem context */
+ element *items; /*%< low water item list */
+ size_t size; /*%< size of each item on this pool */
+ size_t allocated; /*%< # of items currently given out */
+ size_t freecount; /*%< # of items on reserved list */
+ size_t freemax; /*%< # of items allowed on free list */
+ size_t fillcount; /*%< # of items to fetch on each fill */
+ /*%< Stats only. */
+ size_t gets; /*%< # of requests to this pool */
+ /*%< Debugging only. */
+ char name[16]; /*%< printed name in stats reports */
+};
+
+/*
+ * Private Inline-able.
+ */
+
+#if !ISC_MEM_TRACKLINES
+#define ADD_TRACE(a, b, c, d, e)
+#define DELETE_TRACE(a, b, c, d, e)
+#define ISC_MEMFUNC_SCOPE
+#else /* if !ISC_MEM_TRACKLINES */
+#define TRACE_OR_RECORD (ISC_MEM_DEBUGTRACE | ISC_MEM_DEBUGRECORD)
+
+#define SHOULD_TRACE_OR_RECORD(ptr) \
+ ((isc_mem_debugging & TRACE_OR_RECORD) != 0 && ptr != NULL)
+
+#define ADD_TRACE(a, b, c, d, e) \
+ if (SHOULD_TRACE_OR_RECORD(b)) { \
+ add_trace_entry(a, b, c, d, e); \
+ }
+
+#define DELETE_TRACE(a, b, c, d, e) \
+ if (SHOULD_TRACE_OR_RECORD(b)) { \
+ delete_trace_entry(a, b, c, d, e); \
+ }
+
+static void
+print_active(isc_mem_t *ctx, FILE *out);
+#endif /* ISC_MEM_TRACKLINES */
+
+static size_t
+increment_malloced(isc_mem_t *ctx, size_t size) {
+ size_t malloced = atomic_fetch_add_relaxed(&ctx->malloced, size) + size;
+ size_t maxmalloced = atomic_load_relaxed(&ctx->maxmalloced);
+
+ if (malloced > maxmalloced) {
+ atomic_compare_exchange_strong(&ctx->maxmalloced, &maxmalloced,
+ malloced);
+ }
+
+ return (malloced);
+}
+
+static size_t
+decrement_malloced(isc_mem_t *ctx, size_t size) {
+ size_t malloced = atomic_fetch_sub_relaxed(&ctx->malloced, size) - size;
+
+ return (malloced);
+}
+
+#if ISC_MEM_TRACKLINES
+/*!
+ * mctx must not be locked.
+ */
+static void
+add_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size FLARG) {
+ debuglink_t *dl = NULL;
+ uint32_t hash;
+ uint32_t idx;
+
+ MCTXLOCK(mctx);
+
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "add %p size %zu file %s line %u mctx %p\n",
+ ptr, size, file, line, mctx);
+ }
+
+ if (mctx->debuglist == NULL) {
+ goto unlock;
+ }
+
+#ifdef __COVERITY__
+ /*
+ * Use simple conversion from pointer to hash to avoid
+ * tainting 'ptr' due to byte swap in isc_hash_function.
+ */
+ hash = (uintptr_t)ptr >> 3;
+#else
+ hash = isc_hash_function(&ptr, sizeof(ptr), true);
+#endif
+ idx = hash % DEBUG_TABLE_COUNT;
+
+ dl = mallocx(sizeof(*dl), mctx->jemalloc_flags);
+ INSIST(dl != NULL);
+ increment_malloced(mctx, sizeof(*dl));
+
+ ISC_LINK_INIT(dl, link);
+ dl->ptr = ptr;
+ dl->size = size;
+ dl->file = file;
+ dl->line = line;
+
+ ISC_LIST_PREPEND(mctx->debuglist[idx], dl, link);
+ mctx->debuglistcnt++;
+unlock:
+ MCTXUNLOCK(mctx);
+}
+
+static void
+delete_trace_entry(isc_mem_t *mctx, const void *ptr, size_t size,
+ const char *file, unsigned int line) {
+ debuglink_t *dl = NULL;
+ uint32_t hash;
+ uint32_t idx;
+
+ MCTXLOCK(mctx);
+
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "del %p size %zu file %s line %u mctx %p\n",
+ ptr, size, file, line, mctx);
+ }
+
+ if (mctx->debuglist == NULL) {
+ goto unlock;
+ }
+
+#ifdef __COVERITY__
+ /*
+ * Use simple conversion from pointer to hash to avoid
+ * tainting 'ptr' due to byte swap in isc_hash_function.
+ */
+ hash = (uintptr_t)ptr >> 3;
+#else
+ hash = isc_hash_function(&ptr, sizeof(ptr), true);
+#endif
+ idx = hash % DEBUG_TABLE_COUNT;
+
+ dl = ISC_LIST_HEAD(mctx->debuglist[idx]);
+ while (dl != NULL) {
+ if (dl->ptr == ptr) {
+ ISC_LIST_UNLINK(mctx->debuglist[idx], dl, link);
+ decrement_malloced(mctx, sizeof(*dl));
+ sdallocx(dl, sizeof(*dl), mctx->jemalloc_flags);
+ goto unlock;
+ }
+ dl = ISC_LIST_NEXT(dl, link);
+ }
+
+ /*
+ * If we get here, we didn't find the item on the list. We're
+ * screwed.
+ */
+ UNREACHABLE();
+unlock:
+ MCTXUNLOCK(mctx);
+}
+#endif /* ISC_MEM_TRACKLINES */
+
+#define ADJUST_ZERO_ALLOCATION_SIZE(s) \
+ if (s == 0) { \
+ s = ZERO_ALLOCATION_SIZE; \
+ }
+
+#define MEM_ALIGN(a) ((a) ? MALLOCX_ALIGN(a) : 0)
+
+/*!
+ * Perform a malloc, doing memory filling and overrun detection as necessary.
+ */
+static void *
+mem_get(isc_mem_t *ctx, size_t size, int flags) {
+ char *ret = NULL;
+
+ ADJUST_ZERO_ALLOCATION_SIZE(size);
+
+ ret = mallocx(size, flags | ctx->jemalloc_flags);
+ INSIST(ret != NULL);
+
+ if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) {
+ memset(ret, 0xbe, size); /* Mnemonic for "beef". */
+ }
+
+ return (ret);
+}
+
+/*!
+ * Perform a free, doing memory filling and overrun detection as necessary.
+ */
+/* coverity[+free : arg-1] */
+static void
+mem_put(isc_mem_t *ctx, void *mem, size_t size, int flags) {
+ ADJUST_ZERO_ALLOCATION_SIZE(size);
+
+ if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) {
+ memset(mem, 0xde, size); /* Mnemonic for "dead". */
+ }
+ sdallocx(mem, size, flags | ctx->jemalloc_flags);
+}
+
+static void *
+mem_realloc(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size,
+ int flags) {
+ void *new_ptr = NULL;
+
+ ADJUST_ZERO_ALLOCATION_SIZE(new_size);
+
+ new_ptr = rallocx(old_ptr, new_size, flags | ctx->jemalloc_flags);
+ INSIST(new_ptr != NULL);
+
+ if ((ctx->flags & ISC_MEMFLAG_FILL) != 0) {
+ ssize_t diff_size = new_size - old_size;
+ void *diff_ptr = (uint8_t *)new_ptr + old_size;
+ if (diff_size > 0) {
+ /* Mnemonic for "beef". */
+ memset(diff_ptr, 0xbe, diff_size);
+ }
+ }
+
+ return (new_ptr);
+}
+
+#define stats_bucket(ctx, size) \
+ ((size / STATS_BUCKET_SIZE) >= STATS_BUCKETS \
+ ? &ctx->stats[STATS_BUCKETS] \
+ : &ctx->stats[size / STATS_BUCKET_SIZE])
+
+/*!
+ * Update internal counters after a memory get.
+ */
+static void
+mem_getstats(isc_mem_t *ctx, size_t size) {
+ struct stats *stats = stats_bucket(ctx, size);
+
+ atomic_fetch_add_relaxed(&ctx->total, size);
+ atomic_fetch_add_release(&ctx->inuse, size);
+
+ atomic_fetch_add_relaxed(&stats->gets, 1);
+ atomic_fetch_add_relaxed(&stats->totalgets, 1);
+
+ increment_malloced(ctx, size);
+}
+
+/*!
+ * Update internal counters after a memory put.
+ */
+static void
+mem_putstats(isc_mem_t *ctx, void *ptr, size_t size) {
+ struct stats *stats = stats_bucket(ctx, size);
+ atomic_size_t s, g;
+
+ UNUSED(ptr);
+
+ s = atomic_fetch_sub_release(&ctx->inuse, size);
+ INSIST(s >= size);
+
+ g = atomic_fetch_sub_release(&stats->gets, 1);
+ INSIST(g >= 1);
+
+ decrement_malloced(ctx, size);
+}
+
+/*
+ * Private.
+ */
+
+static bool
+mem_jemalloc_arena_create(unsigned int *pnew_arenano) {
+ REQUIRE(pnew_arenano != NULL);
+
+#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4
+ unsigned int arenano = 0;
+ size_t len = sizeof(arenano);
+ int res = 0;
+
+ res = mallctl("arenas.create", &arenano, &len, NULL, 0);
+ if (res != 0) {
+ return (false);
+ }
+
+ *pnew_arenano = arenano;
+
+ return (true);
+#else
+ *pnew_arenano = ISC_MEM_ILLEGAL_ARENA;
+ return (true);
+#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */
+}
+
+static bool
+mem_jemalloc_arena_destroy(unsigned int arenano) {
+#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4
+ int res = 0;
+ char buf[256] = { 0 };
+
+ (void)snprintf(buf, sizeof(buf), "arena.%u.destroy", arenano);
+ res = mallctl(buf, NULL, NULL, NULL, 0);
+ if (res != 0) {
+ return (false);
+ }
+
+ return (true);
+#else
+ UNUSED(arenano);
+ return (true);
+#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */
+}
+
+static void
+mem_initialize(void) {
+ isc_mutex_init(&contextslock);
+ ISC_LIST_INIT(contexts);
+ totallost = 0;
+}
+
+void
+isc__mem_initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&init_once, mem_initialize) == ISC_R_SUCCESS);
+}
+
+static void
+mem_shutdown(void) {
+ isc__mem_checkdestroyed();
+
+ isc_mutex_destroy(&contextslock);
+}
+
+void
+isc__mem_shutdown(void) {
+ RUNTIME_CHECK(isc_once_do(&shut_once, mem_shutdown) == ISC_R_SUCCESS);
+}
+
+static void
+mem_create(isc_mem_t **ctxp, unsigned int flags, unsigned int jemalloc_flags) {
+ isc_mem_t *ctx = NULL;
+
+ REQUIRE(ctxp != NULL && *ctxp == NULL);
+
+ ctx = mallocx(sizeof(*ctx),
+ MALLOCX_ALIGN(isc_os_cacheline()) | jemalloc_flags);
+ INSIST(ctx != NULL);
+
+ *ctx = (isc_mem_t){
+ .magic = MEM_MAGIC,
+ .flags = flags,
+ .jemalloc_flags = jemalloc_flags,
+ .jemalloc_arena = ISC_MEM_ILLEGAL_ARENA,
+ .checkfree = true,
+ };
+
+ isc_mutex_init(&ctx->lock);
+ isc_refcount_init(&ctx->references, 1);
+
+ atomic_init(&ctx->total, 0);
+ atomic_init(&ctx->inuse, 0);
+ atomic_init(&ctx->maxinuse, 0);
+ atomic_init(&ctx->malloced, sizeof(*ctx));
+ atomic_init(&ctx->maxmalloced, sizeof(*ctx));
+ atomic_init(&ctx->hi_water, 0);
+ atomic_init(&ctx->lo_water, 0);
+ atomic_init(&ctx->hi_called, false);
+ atomic_init(&ctx->is_overmem, false);
+
+ for (size_t i = 0; i < STATS_BUCKETS + 1; i++) {
+ atomic_init(&ctx->stats[i].gets, 0);
+ atomic_init(&ctx->stats[i].totalgets, 0);
+ }
+ ISC_LIST_INIT(ctx->pools);
+
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGRECORD) != 0) {
+ unsigned int i;
+
+ ctx->debuglist =
+ mallocx((DEBUG_TABLE_COUNT * sizeof(debuglist_t)),
+ ctx->jemalloc_flags);
+ INSIST(ctx->debuglist != NULL);
+
+ for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
+ ISC_LIST_INIT(ctx->debuglist[i]);
+ }
+ increment_malloced(ctx,
+ DEBUG_TABLE_COUNT * sizeof(debuglist_t));
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+
+ LOCK(&contextslock);
+ ISC_LIST_INITANDAPPEND(contexts, ctx, link);
+ UNLOCK(&contextslock);
+
+ *ctxp = ctx;
+}
+
+/*
+ * Public.
+ */
+
+static void
+destroy(isc_mem_t *ctx) {
+ unsigned int i;
+ size_t malloced;
+ unsigned int arena_no;
+
+ LOCK(&contextslock);
+ ISC_LIST_UNLINK(contexts, ctx, link);
+ totallost += isc_mem_inuse(ctx);
+ UNLOCK(&contextslock);
+
+ ctx->magic = 0;
+
+ arena_no = ctx->jemalloc_arena;
+
+ INSIST(ISC_LIST_EMPTY(ctx->pools));
+
+#if ISC_MEM_TRACKLINES
+ if (ctx->debuglist != NULL) {
+ debuglink_t *dl;
+ for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
+ for (dl = ISC_LIST_HEAD(ctx->debuglist[i]); dl != NULL;
+ dl = ISC_LIST_HEAD(ctx->debuglist[i]))
+ {
+ if (ctx->checkfree && dl->ptr != NULL) {
+ print_active(ctx, stderr);
+ }
+ INSIST(!ctx->checkfree || dl->ptr == NULL);
+
+ ISC_LIST_UNLINK(ctx->debuglist[i], dl, link);
+ sdallocx(dl, sizeof(*dl), ctx->jemalloc_flags);
+ decrement_malloced(ctx, sizeof(*dl));
+ }
+ }
+
+ sdallocx(ctx->debuglist,
+ (DEBUG_TABLE_COUNT * sizeof(debuglist_t)),
+ ctx->jemalloc_flags);
+ decrement_malloced(ctx,
+ DEBUG_TABLE_COUNT * sizeof(debuglist_t));
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+
+ if (ctx->checkfree) {
+ for (i = 0; i <= STATS_BUCKETS; i++) {
+ struct stats *stats = &ctx->stats[i];
+ size_t gets = atomic_load_acquire(&stats->gets);
+ if (gets != 0U) {
+ fprintf(stderr,
+ "Failing assertion due to probable "
+ "leaked memory in context %p (\"%s\") "
+ "(stats[%u].gets == %zu).\n",
+ ctx, ctx->name, i, gets);
+#if ISC_MEM_TRACKLINES
+ print_active(ctx, stderr);
+#endif /* if ISC_MEM_TRACKLINES */
+ INSIST(gets == 0U);
+ }
+ }
+ }
+
+ isc_mutex_destroy(&ctx->lock);
+
+ malloced = decrement_malloced(ctx, sizeof(*ctx));
+
+ if (ctx->checkfree) {
+ INSIST(malloced == 0);
+ }
+ sdallocx(ctx, sizeof(*ctx),
+ MALLOCX_ALIGN(isc_os_cacheline()) | ctx->jemalloc_flags);
+
+ if (arena_no != ISC_MEM_ILLEGAL_ARENA) {
+ RUNTIME_CHECK(mem_jemalloc_arena_destroy(arena_no) == true);
+ }
+}
+
+void
+isc_mem_attach(isc_mem_t *source, isc_mem_t **targetp) {
+ REQUIRE(VALID_CONTEXT(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+isc__mem_detach(isc_mem_t **ctxp FLARG) {
+ isc_mem_t *ctx = NULL;
+
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+
+ ctx = *ctxp;
+ *ctxp = NULL;
+
+ if (isc_refcount_decrement(&ctx->references) == 1) {
+ isc_refcount_destroy(&ctx->references);
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "destroy mctx %p file %s line %u\n",
+ ctx, file, line);
+ }
+#endif
+ destroy(ctx);
+ }
+}
+
+/*
+ * isc_mem_putanddetach() is the equivalent of:
+ *
+ * mctx = NULL;
+ * isc_mem_attach(ptr->mctx, &mctx);
+ * isc_mem_detach(&ptr->mctx);
+ * isc_mem_put(mctx, ptr, sizeof(*ptr);
+ * isc_mem_detach(&mctx);
+ */
+
+void
+isc__mem_putanddetach(isc_mem_t **ctxp, void *ptr, size_t size,
+ size_t alignment FLARG) {
+ isc_mem_t *ctx = NULL;
+
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+ REQUIRE(ptr != NULL);
+ REQUIRE(size != 0);
+
+ ctx = *ctxp;
+ *ctxp = NULL;
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ mem_putstats(ctx, ptr, size);
+ mem_put(ctx, ptr, size, MEM_ALIGN(alignment));
+
+ if (isc_refcount_decrement(&ctx->references) == 1) {
+ isc_refcount_destroy(&ctx->references);
+ destroy(ctx);
+ }
+}
+
+void
+isc__mem_destroy(isc_mem_t **ctxp FLARG) {
+ isc_mem_t *ctx = NULL;
+
+ /*
+ * This routine provides legacy support for callers who use mctxs
+ * without attaching/detaching.
+ */
+
+ REQUIRE(ctxp != NULL && VALID_CONTEXT(*ctxp));
+
+ ctx = *ctxp;
+ *ctxp = NULL;
+
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "destroy mctx %p file %s line %u\n", ctx, file,
+ line);
+ }
+
+ if (isc_refcount_decrement(&ctx->references) > 1) {
+ print_active(ctx, stderr);
+ }
+#else /* if ISC_MEM_TRACKLINES */
+ isc_refcount_decrementz(&ctx->references);
+#endif /* if ISC_MEM_TRACKLINES */
+ isc_refcount_destroy(&ctx->references);
+ destroy(ctx);
+
+ *ctxp = NULL;
+}
+
+#define CALL_HI_WATER(ctx) \
+ { \
+ if (ctx->water != NULL && hi_water(ctx)) { \
+ (ctx->water)(ctx->water_arg, ISC_MEM_HIWATER); \
+ } \
+ }
+
+#define CALL_LO_WATER(ctx) \
+ { \
+ if ((ctx->water != NULL) && lo_water(ctx)) { \
+ (ctx->water)(ctx->water_arg, ISC_MEM_LOWATER); \
+ } \
+ }
+
+static bool
+hi_water(isc_mem_t *ctx) {
+ size_t inuse;
+ size_t maxinuse;
+ size_t hiwater = atomic_load_relaxed(&ctx->hi_water);
+
+ if (hiwater == 0) {
+ return (false);
+ }
+
+ inuse = atomic_load_acquire(&ctx->inuse);
+ if (inuse <= hiwater) {
+ return (false);
+ }
+
+ maxinuse = atomic_load_acquire(&ctx->maxinuse);
+ if (inuse > maxinuse) {
+ (void)atomic_compare_exchange_strong(&ctx->maxinuse, &maxinuse,
+ inuse);
+
+ if ((isc_mem_debugging & ISC_MEM_DEBUGUSAGE) != 0) {
+ fprintf(stderr, "maxinuse = %lu\n",
+ (unsigned long)inuse);
+ }
+ }
+
+ if (atomic_load_acquire(&ctx->hi_called)) {
+ return (false);
+ }
+
+ /* We are over water (for the first time) */
+ atomic_store_release(&ctx->is_overmem, true);
+
+ return (true);
+}
+
+static bool
+lo_water(isc_mem_t *ctx) {
+ size_t inuse;
+ size_t lowater = atomic_load_relaxed(&ctx->lo_water);
+
+ if (lowater == 0) {
+ return (false);
+ }
+
+ inuse = atomic_load_acquire(&ctx->inuse);
+ if (inuse >= lowater) {
+ return (false);
+ }
+
+ if (!atomic_load_acquire(&ctx->hi_called)) {
+ return (false);
+ }
+
+ /* We are no longer overmem */
+ atomic_store_release(&ctx->is_overmem, false);
+
+ return (true);
+}
+
+void *
+isc__mem_get(isc_mem_t *ctx, size_t size, size_t alignment FLARG) {
+ void *ptr = NULL;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ ptr = mem_get(ctx, size, MEM_ALIGN(alignment));
+
+ mem_getstats(ctx, size);
+ ADD_TRACE(ctx, ptr, size, file, line);
+
+ CALL_HI_WATER(ctx);
+
+ return (ptr);
+}
+
+void
+isc__mem_put(isc_mem_t *ctx, void *ptr, size_t size, size_t alignment FLARG) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ mem_putstats(ctx, ptr, size);
+ mem_put(ctx, ptr, size, MEM_ALIGN(alignment));
+
+ CALL_LO_WATER(ctx);
+}
+
+void
+isc_mem_waterack(isc_mem_t *ctx, int flag) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ if (flag == ISC_MEM_LOWATER) {
+ atomic_store(&ctx->hi_called, false);
+ } else if (flag == ISC_MEM_HIWATER) {
+ atomic_store(&ctx->hi_called, true);
+ }
+}
+
+#if ISC_MEM_TRACKLINES
+static void
+print_active(isc_mem_t *mctx, FILE *out) {
+ if (mctx->debuglist != NULL) {
+ debuglink_t *dl;
+ unsigned int i;
+ bool found;
+
+ fprintf(out, "Dump of all outstanding memory "
+ "allocations:\n");
+ found = false;
+ for (i = 0; i < DEBUG_TABLE_COUNT; i++) {
+ dl = ISC_LIST_HEAD(mctx->debuglist[i]);
+
+ if (dl != NULL) {
+ found = true;
+ }
+
+ while (dl != NULL) {
+ if (dl->ptr != NULL) {
+ fprintf(out,
+ "\tptr %p size %zu "
+ "file %s "
+ "line %u\n",
+ dl->ptr, dl->size, dl->file,
+ dl->line);
+ }
+ dl = ISC_LIST_NEXT(dl, link);
+ }
+ }
+
+ if (!found) {
+ fprintf(out, "\tNone.\n");
+ }
+ }
+}
+#endif /* if ISC_MEM_TRACKLINES */
+
+/*
+ * Print the stats[] on the stream "out" with suitable formatting.
+ */
+void
+isc_mem_stats(isc_mem_t *ctx, FILE *out) {
+ isc_mempool_t *pool = NULL;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ MCTXLOCK(ctx);
+
+ for (size_t i = 0; i <= STATS_BUCKETS; i++) {
+ size_t totalgets;
+ size_t gets;
+ struct stats *stats = &ctx->stats[i];
+
+ totalgets = atomic_load_acquire(&stats->totalgets);
+ gets = atomic_load_acquire(&stats->gets);
+
+ if (totalgets != 0U && gets != 0U) {
+ fprintf(out, "%s%5zu: %11zu gets, %11zu rem",
+ (i == STATS_BUCKETS) ? ">=" : " ", i,
+ totalgets, gets);
+ fputc('\n', out);
+ }
+ }
+
+ /*
+ * Note that since a pool can be locked now, these stats might
+ * be somewhat off if the pool is in active use at the time the
+ * stats are dumped. The link fields are protected by the
+ * isc_mem_t's lock, however, so walking this list and
+ * extracting integers from stats fields is always safe.
+ */
+ pool = ISC_LIST_HEAD(ctx->pools);
+ if (pool != NULL) {
+ fprintf(out, "[Pool statistics]\n");
+ fprintf(out, "%15s %10s %10s %10s %10s %10s %10s %1s\n", "name",
+ "size", "allocated", "freecount", "freemax",
+ "fillcount", "gets", "L");
+ }
+ while (pool != NULL) {
+ fprintf(out,
+ "%15s %10zu %10zu %10zu %10zu %10zu %10zu %10zu %s\n",
+ pool->name, pool->size, (size_t)0, pool->allocated,
+ pool->freecount, pool->freemax, pool->fillcount,
+ pool->gets, "N");
+ pool = ISC_LIST_NEXT(pool, link);
+ }
+
+#if ISC_MEM_TRACKLINES
+ print_active(ctx, out);
+#endif /* if ISC_MEM_TRACKLINES */
+
+ MCTXUNLOCK(ctx);
+}
+
+void *
+isc__mem_allocate(isc_mem_t *ctx, size_t size FLARG) {
+ void *ptr = NULL;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ ptr = mem_get(ctx, size, 0);
+
+ /* Recalculate the real allocated size */
+ size = sallocx(ptr, ctx->jemalloc_flags);
+
+ mem_getstats(ctx, size);
+ ADD_TRACE(ctx, ptr, size, file, line);
+
+ CALL_HI_WATER(ctx);
+
+ return (ptr);
+}
+
+void *
+isc__mem_reget(isc_mem_t *ctx, void *old_ptr, size_t old_size, size_t new_size,
+ size_t alignment FLARG) {
+ void *new_ptr = NULL;
+
+ if (old_ptr == NULL) {
+ REQUIRE(old_size == 0);
+ new_ptr = isc__mem_get(ctx, new_size, alignment FLARG_PASS);
+ } else if (new_size == 0) {
+ isc__mem_put(ctx, old_ptr, old_size, alignment FLARG_PASS);
+ } else {
+ DELETE_TRACE(ctx, old_ptr, old_size, file, line);
+ mem_putstats(ctx, old_ptr, old_size);
+
+ new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size,
+ MEM_ALIGN(alignment));
+
+ mem_getstats(ctx, new_size);
+ ADD_TRACE(ctx, new_ptr, new_size, file, line);
+
+ /*
+ * We want to postpone the call to water in edge case
+ * where the realloc will exactly hit on the boundary of
+ * the water and we would call water twice.
+ */
+ CALL_LO_WATER(ctx);
+ CALL_HI_WATER(ctx);
+ }
+
+ return (new_ptr);
+}
+
+void *
+isc__mem_reallocate(isc_mem_t *ctx, void *old_ptr, size_t new_size FLARG) {
+ void *new_ptr = NULL;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ if (old_ptr == NULL) {
+ new_ptr = isc__mem_allocate(ctx, new_size FLARG_PASS);
+ } else if (new_size == 0) {
+ isc__mem_free(ctx, old_ptr FLARG_PASS);
+ } else {
+ size_t old_size = sallocx(old_ptr, ctx->jemalloc_flags);
+
+ DELETE_TRACE(ctx, old_ptr, old_size, file, line);
+ mem_putstats(ctx, old_ptr, old_size);
+
+ new_ptr = mem_realloc(ctx, old_ptr, old_size, new_size, 0);
+
+ /* Recalculate the real allocated size */
+ new_size = sallocx(new_ptr, ctx->jemalloc_flags);
+
+ mem_getstats(ctx, new_size);
+ ADD_TRACE(ctx, new_ptr, new_size, file, line);
+
+ /*
+ * We want to postpone the call to water in edge case
+ * where the realloc will exactly hit on the boundary of
+ * the water and we would call water twice.
+ */
+ CALL_LO_WATER(ctx);
+ CALL_HI_WATER(ctx);
+ }
+
+ return (new_ptr);
+}
+
+void
+isc__mem_free(isc_mem_t *ctx, void *ptr FLARG) {
+ size_t size = 0;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ size = sallocx(ptr, ctx->jemalloc_flags);
+
+ DELETE_TRACE(ctx, ptr, size, file, line);
+
+ mem_putstats(ctx, ptr, size);
+ mem_put(ctx, ptr, size, 0);
+
+ CALL_LO_WATER(ctx);
+}
+
+/*
+ * Other useful things.
+ */
+
+char *
+isc__mem_strdup(isc_mem_t *mctx, const char *s FLARG) {
+ size_t len;
+ char *ns = NULL;
+
+ REQUIRE(VALID_CONTEXT(mctx));
+ REQUIRE(s != NULL);
+
+ len = strlen(s) + 1;
+
+ ns = isc__mem_allocate(mctx, len FLARG_PASS);
+
+ strlcpy(ns, s, len);
+
+ return (ns);
+}
+
+char *
+isc__mem_strndup(isc_mem_t *mctx, const char *s, size_t size FLARG) {
+ size_t len;
+ char *ns = NULL;
+
+ REQUIRE(VALID_CONTEXT(mctx));
+ REQUIRE(s != NULL);
+ REQUIRE(size != 0);
+
+ len = strlen(s) + 1;
+ if (len > size) {
+ len = size;
+ }
+
+ ns = isc__mem_allocate(mctx, len FLARG_PASS);
+
+ strlcpy(ns, s, len);
+
+ return (ns);
+}
+
+void
+isc_mem_setdestroycheck(isc_mem_t *ctx, bool flag) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ MCTXLOCK(ctx);
+
+ ctx->checkfree = flag;
+
+ MCTXUNLOCK(ctx);
+}
+
+size_t
+isc_mem_inuse(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_acquire(&ctx->inuse));
+}
+
+size_t
+isc_mem_maxinuse(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_acquire(&ctx->maxinuse));
+}
+
+size_t
+isc_mem_total(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_acquire(&ctx->total));
+}
+
+size_t
+isc_mem_malloced(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_acquire(&ctx->malloced));
+}
+
+size_t
+isc_mem_maxmalloced(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_acquire(&ctx->maxmalloced));
+}
+
+void
+isc_mem_clearwater(isc_mem_t *mctx) {
+ isc_mem_setwater(mctx, NULL, NULL, 0, 0);
+}
+
+void
+isc_mem_setwater(isc_mem_t *ctx, isc_mem_water_t water, void *water_arg,
+ size_t hiwater, size_t lowater) {
+ isc_mem_water_t oldwater;
+ void *oldwater_arg;
+
+ REQUIRE(VALID_CONTEXT(ctx));
+ REQUIRE(hiwater >= lowater);
+
+ oldwater = ctx->water;
+ oldwater_arg = ctx->water_arg;
+
+ /* No water was set and new water is also NULL */
+ if (oldwater == NULL && water == NULL) {
+ return;
+ }
+
+ /* The water function is being set for the first time */
+ if (oldwater == NULL) {
+ REQUIRE(water != NULL && lowater > 0);
+
+ INSIST(atomic_load(&ctx->hi_water) == 0);
+ INSIST(atomic_load(&ctx->lo_water) == 0);
+
+ ctx->water = water;
+ ctx->water_arg = water_arg;
+ atomic_store(&ctx->hi_water, hiwater);
+ atomic_store(&ctx->lo_water, lowater);
+
+ return;
+ }
+
+ REQUIRE((water == oldwater && water_arg == oldwater_arg) ||
+ (water == NULL && water_arg == NULL && hiwater == 0));
+
+ atomic_store(&ctx->hi_water, hiwater);
+ atomic_store(&ctx->lo_water, lowater);
+
+ if (atomic_load_acquire(&ctx->hi_called) &&
+ (atomic_load_acquire(&ctx->inuse) < lowater || lowater == 0U))
+ {
+ (oldwater)(oldwater_arg, ISC_MEM_LOWATER);
+ }
+}
+
+bool
+isc_mem_isovermem(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ return (atomic_load_relaxed(&ctx->is_overmem));
+}
+
+void
+isc_mem_setname(isc_mem_t *ctx, const char *name) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ LOCK(&ctx->lock);
+ strlcpy(ctx->name, name, sizeof(ctx->name));
+ UNLOCK(&ctx->lock);
+}
+
+const char *
+isc_mem_getname(isc_mem_t *ctx) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ if (ctx->name[0] == 0) {
+ return ("");
+ }
+
+ return (ctx->name);
+}
+
+/*
+ * Memory pool stuff
+ */
+
+void
+isc__mempool_create(isc_mem_t *restrict mctx, const size_t element_size,
+ isc_mempool_t **restrict mpctxp FLARG) {
+ isc_mempool_t *restrict mpctx = NULL;
+ size_t size = element_size;
+
+ REQUIRE(VALID_CONTEXT(mctx));
+ REQUIRE(size > 0U);
+ REQUIRE(mpctxp != NULL && *mpctxp == NULL);
+
+ /*
+ * Mempools are stored as a linked list of element.
+ */
+ if (size < sizeof(element)) {
+ size = sizeof(element);
+ }
+
+ /*
+ * Allocate space for this pool, initialize values, and if all
+ * works well, attach to the memory context.
+ */
+ mpctx = isc_mem_get(mctx, sizeof(isc_mempool_t));
+
+ *mpctx = (isc_mempool_t){
+ .size = size,
+ .freemax = 1,
+ .fillcount = 1,
+ };
+
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "create pool %p file %s line %u mctx %p\n",
+ mpctx, file, line, mctx);
+ }
+#endif /* ISC_MEM_TRACKLINES */
+
+ isc_mem_attach(mctx, &mpctx->mctx);
+ mpctx->magic = MEMPOOL_MAGIC;
+
+ *mpctxp = (isc_mempool_t *)mpctx;
+
+ MCTXLOCK(mctx);
+ ISC_LIST_INITANDAPPEND(mctx->pools, mpctx, link);
+ mctx->poolcnt++;
+ MCTXUNLOCK(mctx);
+}
+
+void
+isc_mempool_setname(isc_mempool_t *restrict mpctx, const char *name) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+ REQUIRE(name != NULL);
+
+ strlcpy(mpctx->name, name, sizeof(mpctx->name));
+}
+
+void
+isc__mempool_destroy(isc_mempool_t **restrict mpctxp FLARG) {
+ isc_mempool_t *restrict mpctx = NULL;
+ isc_mem_t *mctx = NULL;
+ element *restrict item = NULL;
+
+ REQUIRE(mpctxp != NULL);
+ REQUIRE(VALID_MEMPOOL(*mpctxp));
+
+ mpctx = *mpctxp;
+ *mpctxp = NULL;
+
+ mctx = mpctx->mctx;
+
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "destroy pool %p file %s line %u mctx %p\n",
+ mpctx, file, line, mctx);
+ }
+#endif
+
+ if (mpctx->allocated > 0) {
+ UNEXPECTED_ERROR("mempool %s leaked memory", mpctx->name);
+ }
+ REQUIRE(mpctx->allocated == 0);
+
+ /*
+ * Return any items on the free list
+ */
+ while (mpctx->items != NULL) {
+ INSIST(mpctx->freecount > 0);
+ mpctx->freecount--;
+
+ item = mpctx->items;
+ mpctx->items = item->next;
+
+ mem_putstats(mctx, item, mpctx->size);
+ mem_put(mctx, item, mpctx->size, 0);
+ }
+
+ /*
+ * Remove our linked list entry from the memory context.
+ */
+ MCTXLOCK(mctx);
+ ISC_LIST_UNLINK(mctx->pools, mpctx, link);
+ mctx->poolcnt--;
+ MCTXUNLOCK(mctx);
+
+ mpctx->magic = 0;
+
+ isc_mem_putanddetach(&mpctx->mctx, mpctx, sizeof(isc_mempool_t));
+}
+
+void *
+isc__mempool_get(isc_mempool_t *restrict mpctx FLARG) {
+ element *restrict item = NULL;
+
+ REQUIRE(VALID_MEMPOOL(mpctx));
+
+ mpctx->allocated++;
+
+ if (mpctx->items == NULL) {
+ isc_mem_t *mctx = mpctx->mctx;
+#if !__SANITIZE_ADDRESS__
+ const size_t fillcount = mpctx->fillcount;
+#else
+ const size_t fillcount = 1;
+#endif
+ /*
+ * We need to dip into the well. Fill up our free list.
+ */
+ for (size_t i = 0; i < fillcount; i++) {
+ item = mem_get(mctx, mpctx->size, 0);
+ mem_getstats(mctx, mpctx->size);
+ item->next = mpctx->items;
+ mpctx->items = item;
+ mpctx->freecount++;
+ }
+ }
+
+ item = mpctx->items;
+ INSIST(item != NULL);
+
+ mpctx->items = item->next;
+
+ INSIST(mpctx->freecount > 0);
+ mpctx->freecount--;
+ mpctx->gets++;
+
+ ADD_TRACE(mpctx->mctx, item, mpctx->size, file, line);
+
+ return (item);
+}
+
+/* coverity[+free : arg-1] */
+void
+isc__mempool_put(isc_mempool_t *restrict mpctx, void *mem FLARG) {
+ element *restrict item = NULL;
+
+ REQUIRE(VALID_MEMPOOL(mpctx));
+ REQUIRE(mem != NULL);
+
+ isc_mem_t *mctx = mpctx->mctx;
+ const size_t freecount = mpctx->freecount;
+#if !__SANITIZE_ADDRESS__
+ const size_t freemax = mpctx->freemax;
+#else
+ const size_t freemax = 0;
+#endif
+
+ INSIST(mpctx->allocated > 0);
+ mpctx->allocated--;
+
+ DELETE_TRACE(mctx, mem, mpctx->size, file, line);
+
+ /*
+ * If our free list is full, return this to the mctx directly.
+ */
+ if (freecount >= freemax) {
+ mem_putstats(mctx, mem, mpctx->size);
+ mem_put(mctx, mem, mpctx->size, 0);
+ return;
+ }
+
+ /*
+ * Otherwise, attach it to our free list and bump the counter.
+ */
+ item = (element *)mem;
+ item->next = mpctx->items;
+ mpctx->items = item;
+ mpctx->freecount++;
+}
+
+/*
+ * Quotas
+ */
+
+void
+isc_mempool_setfreemax(isc_mempool_t *restrict mpctx,
+ const unsigned int limit) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+ mpctx->freemax = limit;
+}
+
+unsigned int
+isc_mempool_getfreemax(isc_mempool_t *restrict mpctx) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+
+ return (mpctx->freemax);
+}
+
+unsigned int
+isc_mempool_getfreecount(isc_mempool_t *restrict mpctx) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+
+ return (mpctx->freecount);
+}
+
+unsigned int
+isc_mempool_getallocated(isc_mempool_t *restrict mpctx) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+
+ return (mpctx->allocated);
+}
+
+void
+isc_mempool_setfillcount(isc_mempool_t *restrict mpctx,
+ unsigned int const limit) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+ REQUIRE(limit > 0);
+
+ mpctx->fillcount = limit;
+}
+
+unsigned int
+isc_mempool_getfillcount(isc_mempool_t *restrict mpctx) {
+ REQUIRE(VALID_MEMPOOL(mpctx));
+
+ return (mpctx->fillcount);
+}
+
+/*
+ * Requires contextslock to be held by caller.
+ */
+#if ISC_MEM_TRACKLINES
+static void
+print_contexts(FILE *file) {
+ isc_mem_t *ctx;
+
+ for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
+ ctx = ISC_LIST_NEXT(ctx, link))
+ {
+ fprintf(file, "context: %p (%s): %" PRIuFAST32 " references\n",
+ ctx, ctx->name[0] == 0 ? "<unknown>" : ctx->name,
+ isc_refcount_current(&ctx->references));
+ print_active(ctx, file);
+ }
+ fflush(file);
+}
+#endif
+
+static atomic_uintptr_t checkdestroyed = 0;
+
+void
+isc_mem_checkdestroyed(FILE *file) {
+ atomic_store_release(&checkdestroyed, (uintptr_t)file);
+}
+
+void
+isc__mem_checkdestroyed(void) {
+ FILE *file = (FILE *)atomic_load_acquire(&checkdestroyed);
+
+ if (file == NULL) {
+ return;
+ }
+
+ LOCK(&contextslock);
+ if (!ISC_LIST_EMPTY(contexts)) {
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & TRACE_OR_RECORD) != 0) {
+ print_contexts(file);
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+ UNREACHABLE();
+ }
+ UNLOCK(&contextslock);
+}
+
+unsigned int
+isc_mem_references(isc_mem_t *ctx) {
+ return (isc_refcount_current(&ctx->references));
+}
+
+typedef struct summarystat {
+ uint64_t total;
+ uint64_t inuse;
+ uint64_t malloced;
+ uint64_t contextsize;
+} summarystat_t;
+
+#ifdef HAVE_LIBXML2
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+static int
+xml_renderctx(isc_mem_t *ctx, summarystat_t *summary, xmlTextWriterPtr writer) {
+ REQUIRE(VALID_CONTEXT(ctx));
+
+ int xmlrc;
+
+ MCTXLOCK(ctx);
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "context"));
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%p", ctx));
+ TRY0(xmlTextWriterEndElement(writer)); /* id */
+
+ if (ctx->name[0] != 0) {
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "name"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s", ctx->name));
+ TRY0(xmlTextWriterEndElement(writer)); /* name */
+ }
+
+ summary->contextsize += sizeof(*ctx);
+#if ISC_MEM_TRACKLINES
+ if (ctx->debuglist != NULL) {
+ summary->contextsize += DEBUG_TABLE_COUNT *
+ sizeof(debuglist_t) +
+ ctx->debuglistcnt * sizeof(debuglink_t);
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "references"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIuFAST32,
+ isc_refcount_current(&ctx->references)));
+ TRY0(xmlTextWriterEndElement(writer)); /* references */
+
+ summary->total += isc_mem_total(ctx);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "total"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)isc_mem_total(ctx)));
+ TRY0(xmlTextWriterEndElement(writer)); /* total */
+
+ summary->inuse += isc_mem_inuse(ctx);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "inuse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)isc_mem_inuse(ctx)));
+ TRY0(xmlTextWriterEndElement(writer)); /* inuse */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxinuse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)isc_mem_maxinuse(ctx)));
+ TRY0(xmlTextWriterEndElement(writer)); /* maxinuse */
+
+ summary->malloced += isc_mem_malloced(ctx);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "malloced"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ (uint64_t)isc_mem_malloced(ctx)));
+ TRY0(xmlTextWriterEndElement(writer)); /* malloced */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "maxmalloced"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIu64 "", (uint64_t)isc_mem_maxmalloced(ctx)));
+ TRY0(xmlTextWriterEndElement(writer)); /* maxmalloced */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "pools"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%u", ctx->poolcnt));
+ TRY0(xmlTextWriterEndElement(writer)); /* pools */
+ summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t);
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "hiwater"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIu64 "",
+ (uint64_t)atomic_load_relaxed(&ctx->hi_water)));
+ TRY0(xmlTextWriterEndElement(writer)); /* hiwater */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "lowater"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIu64 "",
+ (uint64_t)atomic_load_relaxed(&ctx->lo_water)));
+ TRY0(xmlTextWriterEndElement(writer)); /* lowater */
+
+ TRY0(xmlTextWriterEndElement(writer)); /* context */
+
+error:
+ MCTXUNLOCK(ctx);
+
+ return (xmlrc);
+}
+
+int
+isc_mem_renderxml(void *writer0) {
+ isc_mem_t *ctx;
+ summarystat_t summary = { 0 };
+ uint64_t lost;
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "contexts"));
+
+ LOCK(&contextslock);
+ lost = totallost;
+ for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
+ ctx = ISC_LIST_NEXT(ctx, link))
+ {
+ xmlrc = xml_renderctx(ctx, &summary, writer);
+ if (xmlrc < 0) {
+ UNLOCK(&contextslock);
+ goto error;
+ }
+ }
+ UNLOCK(&contextslock);
+
+ TRY0(xmlTextWriterEndElement(writer)); /* contexts */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "summary"));
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "TotalUse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ summary.total));
+ TRY0(xmlTextWriterEndElement(writer)); /* TotalUse */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "InUse"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ summary.inuse));
+ TRY0(xmlTextWriterEndElement(writer)); /* InUse */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Malloced"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ summary.malloced));
+ TRY0(xmlTextWriterEndElement(writer)); /* InUse */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "ContextSize"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "",
+ summary.contextsize));
+ TRY0(xmlTextWriterEndElement(writer)); /* ContextSize */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "Lost"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%" PRIu64 "", lost));
+ TRY0(xmlTextWriterEndElement(writer)); /* Lost */
+
+ TRY0(xmlTextWriterEndElement(writer)); /* summary */
+error:
+ return (xmlrc);
+}
+
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#define CHECKMEM(m) RUNTIME_CHECK(m != NULL)
+
+static isc_result_t
+json_renderctx(isc_mem_t *ctx, summarystat_t *summary, json_object *array) {
+ REQUIRE(VALID_CONTEXT(ctx));
+ REQUIRE(summary != NULL);
+ REQUIRE(array != NULL);
+
+ json_object *ctxobj, *obj;
+ char buf[1024];
+
+ MCTXLOCK(ctx);
+
+ summary->contextsize += sizeof(*ctx);
+ summary->total += isc_mem_total(ctx);
+ summary->inuse += isc_mem_inuse(ctx);
+ summary->malloced += isc_mem_malloced(ctx);
+#if ISC_MEM_TRACKLINES
+ if (ctx->debuglist != NULL) {
+ summary->contextsize += DEBUG_TABLE_COUNT *
+ sizeof(debuglist_t) +
+ ctx->debuglistcnt * sizeof(debuglink_t);
+ }
+#endif /* if ISC_MEM_TRACKLINES */
+
+ ctxobj = json_object_new_object();
+ CHECKMEM(ctxobj);
+
+ snprintf(buf, sizeof(buf), "%p", ctx);
+ obj = json_object_new_string(buf);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "id", obj);
+
+ if (ctx->name[0] != 0) {
+ obj = json_object_new_string(ctx->name);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "name", obj);
+ }
+
+ obj = json_object_new_int64(isc_refcount_current(&ctx->references));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "references", obj);
+
+ obj = json_object_new_int64(isc_mem_total(ctx));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "total", obj);
+
+ obj = json_object_new_int64(isc_mem_inuse(ctx));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "inuse", obj);
+
+ obj = json_object_new_int64(isc_mem_maxinuse(ctx));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "maxinuse", obj);
+
+ obj = json_object_new_int64(isc_mem_malloced(ctx));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "malloced", obj);
+
+ obj = json_object_new_int64(isc_mem_maxmalloced(ctx));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "maxmalloced", obj);
+
+ obj = json_object_new_int64(ctx->poolcnt);
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "pools", obj);
+
+ summary->contextsize += ctx->poolcnt * sizeof(isc_mempool_t);
+
+ obj = json_object_new_int64(atomic_load_relaxed(&ctx->hi_water));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "hiwater", obj);
+
+ obj = json_object_new_int64(atomic_load_relaxed(&ctx->lo_water));
+ CHECKMEM(obj);
+ json_object_object_add(ctxobj, "lowater", obj);
+
+ MCTXUNLOCK(ctx);
+ json_object_array_add(array, ctxobj);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_mem_renderjson(void *memobj0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_mem_t *ctx;
+ summarystat_t summary = { 0 };
+ uint64_t lost;
+ json_object *ctxarray, *obj;
+ json_object *memobj = (json_object *)memobj0;
+
+ ctxarray = json_object_new_array();
+ CHECKMEM(ctxarray);
+
+ LOCK(&contextslock);
+ lost = totallost;
+ for (ctx = ISC_LIST_HEAD(contexts); ctx != NULL;
+ ctx = ISC_LIST_NEXT(ctx, link))
+ {
+ result = json_renderctx(ctx, &summary, ctxarray);
+ if (result != ISC_R_SUCCESS) {
+ UNLOCK(&contextslock);
+ goto error;
+ }
+ }
+ UNLOCK(&contextslock);
+
+ obj = json_object_new_int64(summary.total);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "TotalUse", obj);
+
+ obj = json_object_new_int64(summary.inuse);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "InUse", obj);
+
+ obj = json_object_new_int64(summary.malloced);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "Malloced", obj);
+
+ obj = json_object_new_int64(summary.contextsize);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "ContextSize", obj);
+
+ obj = json_object_new_int64(lost);
+ CHECKMEM(obj);
+ json_object_object_add(memobj, "Lost", obj);
+
+ json_object_object_add(memobj, "contexts", ctxarray);
+ return (ISC_R_SUCCESS);
+
+error:
+ if (ctxarray != NULL) {
+ json_object_put(ctxarray);
+ }
+ return (result);
+}
+#endif /* HAVE_JSON_C */
+
+void
+isc__mem_create(isc_mem_t **mctxp FLARG) {
+ mem_create(mctxp, isc_mem_defaultflags, 0);
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr, "create mctx %p file %s line %u\n", *mctxp,
+ file, line);
+ }
+#endif /* ISC_MEM_TRACKLINES */
+}
+
+void
+isc__mem_create_arena(isc_mem_t **mctxp FLARG) {
+ unsigned int arena_no = ISC_MEM_ILLEGAL_ARENA;
+
+ RUNTIME_CHECK(mem_jemalloc_arena_create(&arena_no));
+
+ /*
+ * We use MALLOCX_TCACHE_NONE to bypass the tcache and route
+ * allocations directly to the arena. That is a recommendation
+ * from jemalloc developers:
+ *
+ * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1698173849
+ */
+ mem_create(mctxp, isc_mem_defaultflags,
+ arena_no == ISC_MEM_ILLEGAL_ARENA
+ ? 0
+ : MALLOCX_ARENA(arena_no) | MALLOCX_TCACHE_NONE);
+ (*mctxp)->jemalloc_arena = arena_no;
+#if ISC_MEM_TRACKLINES
+ if ((isc_mem_debugging & ISC_MEM_DEBUGTRACE) != 0) {
+ fprintf(stderr,
+ "create mctx %p file %s line %u for jemalloc arena "
+ "%u\n",
+ *mctxp, file, line, arena_no);
+ }
+#endif /* ISC_MEM_TRACKLINES */
+}
+
+#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4
+static bool
+jemalloc_set_ssize_value(const char *valname, ssize_t newval) {
+ int ret;
+
+ ret = mallctl(valname, NULL, NULL, &newval, sizeof(newval));
+ return (ret == 0);
+}
+#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */
+
+static isc_result_t
+mem_set_arena_ssize_value(isc_mem_t *mctx, const char *arena_valname,
+ const ssize_t newval) {
+ REQUIRE(VALID_CONTEXT(mctx));
+#if defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4
+ bool ret;
+ char buf[256] = { 0 };
+
+ if (mctx->jemalloc_arena == ISC_MEM_ILLEGAL_ARENA) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ (void)snprintf(buf, sizeof(buf), "arena.%u.%s", mctx->jemalloc_arena,
+ arena_valname);
+
+ ret = jemalloc_set_ssize_value(buf, newval);
+
+ if (!ret) {
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+#else
+ UNUSED(arena_valname);
+ UNUSED(newval);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* defined(JEMALLOC_API_SUPPORTED) && JEMALLOC_VERSION_MAJOR >= 4 */
+}
+
+isc_result_t
+isc_mem_arena_set_muzzy_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) {
+ return (mem_set_arena_ssize_value(mctx, "muzzy_decay_ms", decay_ms));
+}
+
+isc_result_t
+isc_mem_arena_set_dirty_decay_ms(isc_mem_t *mctx, const ssize_t decay_ms) {
+ return (mem_set_arena_ssize_value(mctx, "dirty_decay_ms", decay_ms));
+}
+
+void
+isc__mem_printactive(isc_mem_t *ctx, FILE *file) {
+#if ISC_MEM_TRACKLINES
+ REQUIRE(VALID_CONTEXT(ctx));
+ REQUIRE(file != NULL);
+
+ print_active(ctx, file);
+#else /* if ISC_MEM_TRACKLINES */
+ UNUSED(ctx);
+ UNUSED(file);
+#endif /* if ISC_MEM_TRACKLINES */
+}
diff --git a/lib/isc/mem_p.h b/lib/isc/mem_p.h
new file mode 100644
index 0000000..611a025
--- /dev/null
+++ b/lib/isc/mem_p.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
+
+#include <stdio.h>
+
+#include <isc/mem.h>
+
+/*! \file */
+
+void
+isc__mem_printactive(isc_mem_t *mctx, FILE *file);
+/*%<
+ * For use by unit tests, prints active memory blocks for
+ * a single memory context.
+ */
+
+void
+isc__mem_checkdestroyed(void);
+
+void
+isc__mem_initialize(void);
+
+void
+isc__mem_shutdown(void);
diff --git a/lib/isc/meminfo.c b/lib/isc/meminfo.c
new file mode 100644
index 0000000..612ccea
--- /dev/null
+++ b/lib/isc/meminfo.c
@@ -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.
+ */
+
+#include <inttypes.h>
+#include <unistd.h>
+
+#include <isc/meminfo.h>
+#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__)
+#include <sys/sysctl.h>
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */
+
+uint64_t
+isc_meminfo_totalphys(void) {
+#if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE))
+ int mib[2];
+ mib[0] = CTL_HW;
+#if defined(HW_MEMSIZE)
+ mib[1] = HW_MEMSIZE;
+#elif defined(HW_PHYSMEM64)
+ mib[1] = HW_PHYSMEM64;
+#endif /* if defined(HW_MEMSIZE) */
+ uint64_t size = 0;
+ size_t len = sizeof(size);
+ if (sysctl(mib, 2, &size, &len, NULL, 0) == 0) {
+ return (size);
+ }
+#endif /* if defined(CTL_HW) && (defined(HW_PHYSMEM64) || defined(HW_MEMSIZE)) \
+ * */
+#if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
+ long pages = sysconf(_SC_PHYS_PAGES);
+ long pagesize = sysconf(_SC_PAGESIZE);
+
+ if (pages == -1 || pagesize == -1) {
+ return (0);
+ }
+
+ return ((size_t)pages * pagesize);
+#endif /* if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) */
+ return (0);
+}
diff --git a/lib/isc/mutex.c b/lib/isc/mutex.c
new file mode 100644
index 0000000..65f1663
--- /dev/null
+++ b/lib/isc/mutex.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <isc/mutex.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP
+static bool attr_initialized = false;
+static pthread_mutexattr_t attr;
+static isc_once_t once_attr = ISC_ONCE_INIT;
+
+static void
+initialize_attr(void) {
+ RUNTIME_CHECK(pthread_mutexattr_init(&attr) == 0);
+ RUNTIME_CHECK(pthread_mutexattr_settype(
+ &attr, PTHREAD_MUTEX_ADAPTIVE_NP) == 0);
+ attr_initialized = true;
+}
+#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+
+int
+isc__mutex_init(isc_mutex_t *mp) {
+#ifdef HAVE_PTHREAD_MUTEX_ADAPTIVE_NP
+ isc_result_t result = ISC_R_SUCCESS;
+ result = isc_once_do(&once_attr, initialize_attr);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ return (pthread_mutex_init(mp, &attr));
+#else /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+ return (pthread_mutex_init(mp, NULL));
+#endif /* HAVE_PTHREAD_MUTEX_ADAPTIVE_NP */
+}
diff --git a/lib/isc/mutexblock.c b/lib/isc/mutexblock.c
new file mode 100644
index 0000000..56a2985
--- /dev/null
+++ b/lib/isc/mutexblock.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <isc/mutexblock.h>
+#include <isc/util.h>
+
+void
+isc_mutexblock_init(isc_mutex_t *block, unsigned int count) {
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ isc_mutex_init(&block[i]);
+ }
+}
+
+void
+isc_mutexblock_destroy(isc_mutex_t *block, unsigned int count) {
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ isc_mutex_destroy(&block[i]);
+ }
+}
diff --git a/lib/isc/net.c b/lib/isc/net.c
new file mode 100644
index 0000000..e32a543
--- /dev/null
+++ b/lib/isc/net.c
@@ -0,0 +1,497 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <sys/types.h>
+
+#if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__)
+#if defined(HAVE_SYS_PARAM_H)
+#include <sys/param.h>
+#endif /* if defined(HAVE_SYS_PARAM_H) */
+#include <sys/sysctl.h>
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && !defined(__linux__) */
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include <isc/log.h>
+#include <isc/net.h>
+#include <isc/netdb.h>
+#include <isc/once.h>
+#include <isc/strerr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#ifndef socklen_t
+#define socklen_t unsigned int
+#endif /* ifndef socklen_t */
+
+/*%
+ * Definitions about UDP port range specification. This is a total mess of
+ * portability variants: some use sysctl (but the sysctl names vary), some use
+ * system-specific interfaces, some have the same interface for IPv4 and IPv6,
+ * some separate them, etc...
+ */
+
+/*%
+ * The last resort defaults: use all non well known port space
+ */
+#ifndef ISC_NET_PORTRANGELOW
+#define ISC_NET_PORTRANGELOW 1024
+#endif /* ISC_NET_PORTRANGELOW */
+#ifndef ISC_NET_PORTRANGEHIGH
+#define ISC_NET_PORTRANGEHIGH 65535
+#endif /* ISC_NET_PORTRANGEHIGH */
+
+#ifdef HAVE_SYSCTLBYNAME
+
+/*%
+ * sysctl variants
+ */
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__)
+#define USE_SYSCTL_PORTRANGE
+#define SYSCTL_V4PORTRANGE_LOW "net.inet.ip.portrange.hifirst"
+#define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.portrange.hilast"
+#define SYSCTL_V6PORTRANGE_LOW "net.inet.ip.portrange.hifirst"
+#define SYSCTL_V6PORTRANGE_HIGH "net.inet.ip.portrange.hilast"
+#endif /* if defined(__FreeBSD__) || defined(__APPLE__) || \
+ * defined(__DragonFly__) */
+
+#ifdef __NetBSD__
+#define USE_SYSCTL_PORTRANGE
+#define SYSCTL_V4PORTRANGE_LOW "net.inet.ip.anonportmin"
+#define SYSCTL_V4PORTRANGE_HIGH "net.inet.ip.anonportmax"
+#define SYSCTL_V6PORTRANGE_LOW "net.inet6.ip6.anonportmin"
+#define SYSCTL_V6PORTRANGE_HIGH "net.inet6.ip6.anonportmax"
+#endif /* ifdef __NetBSD__ */
+
+#else /* !HAVE_SYSCTLBYNAME */
+
+#ifdef __OpenBSD__
+#define USE_SYSCTL_PORTRANGE
+#define SYSCTL_V4PORTRANGE_LOW \
+ { \
+ CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HIFIRSTAUTO \
+ }
+#define SYSCTL_V4PORTRANGE_HIGH \
+ { \
+ CTL_NET, PF_INET, IPPROTO_IP, IPCTL_IPPORT_HILASTAUTO \
+ }
+/* Same for IPv6 */
+#define SYSCTL_V6PORTRANGE_LOW SYSCTL_V4PORTRANGE_LOW
+#define SYSCTL_V6PORTRANGE_HIGH SYSCTL_V4PORTRANGE_HIGH
+#endif /* ifdef __OpenBSD__ */
+
+#endif /* HAVE_SYSCTLBYNAME */
+
+static isc_once_t once_ipv6only = ISC_ONCE_INIT;
+#ifdef __notyet__
+static isc_once_t once_ipv6pktinfo = ISC_ONCE_INIT;
+#endif /* ifdef __notyet__ */
+
+#ifndef ISC_CMSG_IP_TOS
+#ifdef __APPLE__
+#define ISC_CMSG_IP_TOS 0 /* As of 10.8.2. */
+#else /* ! __APPLE__ */
+#define ISC_CMSG_IP_TOS 1
+#endif /* ! __APPLE__ */
+#endif /* ! ISC_CMSG_IP_TOS */
+
+static isc_once_t once = ISC_ONCE_INIT;
+
+static isc_result_t ipv4_result = ISC_R_NOTFOUND;
+static isc_result_t ipv6_result = ISC_R_NOTFOUND;
+static isc_result_t unix_result = ISC_R_NOTFOUND;
+static isc_result_t ipv6only_result = ISC_R_NOTFOUND;
+static isc_result_t ipv6pktinfo_result = ISC_R_NOTFOUND;
+
+static isc_result_t
+try_proto(int domain) {
+ int s;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ s = socket(domain, SOCK_STREAM, 0);
+ if (s == -1) {
+ switch (errno) {
+#ifdef EAFNOSUPPORT
+ case EAFNOSUPPORT:
+#endif /* ifdef EAFNOSUPPORT */
+#ifdef EPFNOSUPPORT
+ case EPFNOSUPPORT:
+#endif /* ifdef EPFNOSUPPORT */
+#ifdef EPROTONOSUPPORT
+ case EPROTONOSUPPORT:
+#endif /* ifdef EPROTONOSUPPORT */
+#ifdef EINVAL
+ case EINVAL:
+#endif /* ifdef EINVAL */
+ return (ISC_R_NOTFOUND);
+ default:
+ UNEXPECTED_SYSERROR(errno, "socket()");
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ if (domain == PF_INET6) {
+ struct sockaddr_in6 sin6;
+ unsigned int len;
+
+ /*
+ * Check to see if IPv6 is broken, as is common on Linux.
+ */
+ len = sizeof(sin6);
+ if (getsockname(s, (struct sockaddr *)&sin6, (void *)&len) < 0)
+ {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "retrieving the address of an IPv6 "
+ "socket from the kernel failed.");
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET, ISC_LOG_ERROR,
+ "IPv6 is not supported.");
+ result = ISC_R_NOTFOUND;
+ } else {
+ if (len == sizeof(struct sockaddr_in6)) {
+ result = ISC_R_SUCCESS;
+ } else {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET,
+ ISC_LOG_ERROR,
+ "IPv6 structures in kernel and "
+ "user space do not match.");
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_SOCKET,
+ ISC_LOG_ERROR,
+ "IPv6 is not supported.");
+ result = ISC_R_NOTFOUND;
+ }
+ }
+ }
+
+ (void)close(s);
+
+ return (result);
+}
+
+static void
+initialize_action(void) {
+ ipv4_result = try_proto(PF_INET);
+ ipv6_result = try_proto(PF_INET6);
+ unix_result = try_proto(PF_UNIX);
+}
+
+static void
+initialize(void) {
+ RUNTIME_CHECK(isc_once_do(&once, initialize_action) == ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_net_probeipv4(void) {
+ initialize();
+ return (ipv4_result);
+}
+
+isc_result_t
+isc_net_probeipv6(void) {
+ initialize();
+ return (ipv6_result);
+}
+
+isc_result_t
+isc_net_probeunix(void) {
+ initialize();
+ return (unix_result);
+}
+
+static void
+try_ipv6only(void) {
+#ifdef IPV6_V6ONLY
+ int s, on;
+#endif /* ifdef IPV6_V6ONLY */
+ isc_result_t result;
+
+ result = isc_net_probeipv6();
+ if (result != ISC_R_SUCCESS) {
+ ipv6only_result = result;
+ return;
+ }
+
+#ifndef IPV6_V6ONLY
+ ipv6only_result = ISC_R_NOTFOUND;
+ return;
+#else /* ifndef IPV6_V6ONLY */
+ /* check for TCP sockets */
+ s = socket(PF_INET6, SOCK_STREAM, 0);
+ if (s == -1) {
+ UNEXPECTED_SYSERROR(errno, "socket()");
+ ipv6only_result = ISC_R_UNEXPECTED;
+ return;
+ }
+
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) {
+ ipv6only_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ close(s);
+
+ /* check for UDP sockets */
+ s = socket(PF_INET6, SOCK_DGRAM, 0);
+ if (s == -1) {
+ UNEXPECTED_SYSERROR(errno, "socket()");
+ ipv6only_result = ISC_R_UNEXPECTED;
+ return;
+ }
+
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) {
+ ipv6only_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ ipv6only_result = ISC_R_SUCCESS;
+
+close:
+ close(s);
+ return;
+#endif /* IPV6_V6ONLY */
+}
+
+static void
+initialize_ipv6only(void) {
+ RUNTIME_CHECK(isc_once_do(&once_ipv6only, try_ipv6only) ==
+ ISC_R_SUCCESS);
+}
+
+#ifdef __notyet__
+static void
+try_ipv6pktinfo(void) {
+ int s, on;
+ isc_result_t result;
+ int optname;
+
+ result = isc_net_probeipv6();
+ if (result != ISC_R_SUCCESS) {
+ ipv6pktinfo_result = result;
+ return;
+ }
+
+ /* we only use this for UDP sockets */
+ s = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ if (s == -1) {
+ UNEXPECTED_SYSERROR(errno, "socket()");
+ ipv6pktinfo_result = ISC_R_UNEXPECTED;
+ return;
+ }
+
+#ifdef IPV6_RECVPKTINFO
+ optname = IPV6_RECVPKTINFO;
+#else /* ifdef IPV6_RECVPKTINFO */
+ optname = IPV6_PKTINFO;
+#endif /* ifdef IPV6_RECVPKTINFO */
+ on = 1;
+ if (setsockopt(s, IPPROTO_IPV6, optname, &on, sizeof(on)) < 0) {
+ ipv6pktinfo_result = ISC_R_NOTFOUND;
+ goto close;
+ }
+
+ ipv6pktinfo_result = ISC_R_SUCCESS;
+
+close:
+ close(s);
+ return;
+}
+
+static void
+initialize_ipv6pktinfo(void) {
+ RUNTIME_CHECK(isc_once_do(&once_ipv6pktinfo, try_ipv6pktinfo) ==
+ ISC_R_SUCCESS);
+}
+#endif /* ifdef __notyet__ */
+
+isc_result_t
+isc_net_probe_ipv6only(void) {
+ initialize_ipv6only();
+ return (ipv6only_result);
+}
+
+isc_result_t
+isc_net_probe_ipv6pktinfo(void) {
+/*
+ * XXXWPK if pktinfo were supported then we could listen on :: for ipv6 and get
+ * the information about the destination address from pktinfo structure passed
+ * in recvmsg but this method is not portable and libuv doesn't support it - so
+ * we need to listen on all interfaces.
+ * We should verify that this doesn't impact performance (we already do it for
+ * ipv4) and either remove all the ipv6pktinfo detection code from above
+ * or think of fixing libuv.
+ */
+#ifdef __notyet__
+ initialize_ipv6pktinfo();
+#endif /* ifdef __notyet__ */
+ return (ipv6pktinfo_result);
+}
+
+#if defined(USE_SYSCTL_PORTRANGE)
+#if defined(HAVE_SYSCTLBYNAME)
+static isc_result_t
+getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) {
+ int port_low, port_high;
+ size_t portlen;
+ const char *sysctlname_lowport, *sysctlname_hiport;
+
+ if (af == AF_INET) {
+ sysctlname_lowport = SYSCTL_V4PORTRANGE_LOW;
+ sysctlname_hiport = SYSCTL_V4PORTRANGE_HIGH;
+ } else {
+ sysctlname_lowport = SYSCTL_V6PORTRANGE_LOW;
+ sysctlname_hiport = SYSCTL_V6PORTRANGE_HIGH;
+ }
+ portlen = sizeof(port_low);
+ if (sysctlbyname(sysctlname_lowport, &port_low, &portlen, NULL, 0) < 0)
+ {
+ return (ISC_R_FAILURE);
+ }
+ portlen = sizeof(port_high);
+ if (sysctlbyname(sysctlname_hiport, &port_high, &portlen, NULL, 0) < 0)
+ {
+ return (ISC_R_FAILURE);
+ }
+ if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) {
+ return (ISC_R_RANGE);
+ }
+
+ *low = (in_port_t)port_low;
+ *high = (in_port_t)port_high;
+
+ return (ISC_R_SUCCESS);
+}
+#else /* !HAVE_SYSCTLBYNAME */
+static isc_result_t
+getudpportrange_sysctl(int af, in_port_t *low, in_port_t *high) {
+ int mib_lo4[4] = SYSCTL_V4PORTRANGE_LOW;
+ int mib_hi4[4] = SYSCTL_V4PORTRANGE_HIGH;
+ int mib_lo6[4] = SYSCTL_V6PORTRANGE_LOW;
+ int mib_hi6[4] = SYSCTL_V6PORTRANGE_HIGH;
+ int *mib_lo, *mib_hi, miblen;
+ int port_low, port_high;
+ size_t portlen;
+
+ if (af == AF_INET) {
+ mib_lo = mib_lo4;
+ mib_hi = mib_hi4;
+ miblen = sizeof(mib_lo4) / sizeof(mib_lo4[0]);
+ } else {
+ mib_lo = mib_lo6;
+ mib_hi = mib_hi6;
+ miblen = sizeof(mib_lo6) / sizeof(mib_lo6[0]);
+ }
+
+ portlen = sizeof(port_low);
+ if (sysctl(mib_lo, miblen, &port_low, &portlen, NULL, 0) < 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ portlen = sizeof(port_high);
+ if (sysctl(mib_hi, miblen, &port_high, &portlen, NULL, 0) < 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ if ((port_low & ~0xffff) != 0 || (port_high & ~0xffff) != 0) {
+ return (ISC_R_RANGE);
+ }
+
+ *low = (in_port_t)port_low;
+ *high = (in_port_t)port_high;
+
+ return (ISC_R_SUCCESS);
+}
+#endif /* HAVE_SYSCTLBYNAME */
+#endif /* USE_SYSCTL_PORTRANGE */
+
+isc_result_t
+isc_net_getudpportrange(int af, in_port_t *low, in_port_t *high) {
+ int result = ISC_R_FAILURE;
+#if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux)
+ FILE *fp;
+#endif /* if !defined(USE_SYSCTL_PORTRANGE) && defined(__linux) */
+
+ REQUIRE(low != NULL && high != NULL);
+
+#if defined(USE_SYSCTL_PORTRANGE)
+ result = getudpportrange_sysctl(af, low, high);
+#elif defined(__linux)
+
+ UNUSED(af);
+
+ /*
+ * Linux local ports are address family agnostic.
+ */
+ fp = fopen("/proc/sys/net/ipv4/ip_local_port_range", "r");
+ if (fp != NULL) {
+ int n;
+ unsigned int l, h;
+
+ n = fscanf(fp, "%u %u", &l, &h);
+ if (n == 2 && (l & ~0xffff) == 0 && (h & ~0xffff) == 0) {
+ *low = l;
+ *high = h;
+ result = ISC_R_SUCCESS;
+ }
+ fclose(fp);
+ }
+#else /* if defined(USE_SYSCTL_PORTRANGE) */
+ UNUSED(af);
+#endif /* if defined(USE_SYSCTL_PORTRANGE) */
+
+ if (result != ISC_R_SUCCESS) {
+ *low = ISC_NET_PORTRANGELOW;
+ *high = ISC_NET_PORTRANGEHIGH;
+ }
+
+ return (ISC_R_SUCCESS); /* we currently never fail in this function */
+}
+
+void
+isc_net_disableipv4(void) {
+ initialize();
+ if (ipv4_result == ISC_R_SUCCESS) {
+ ipv4_result = ISC_R_DISABLED;
+ }
+}
+
+void
+isc_net_disableipv6(void) {
+ initialize();
+ if (ipv6_result == ISC_R_SUCCESS) {
+ ipv6_result = ISC_R_DISABLED;
+ }
+}
+
+void
+isc_net_enableipv4(void) {
+ initialize();
+ if (ipv4_result == ISC_R_DISABLED) {
+ ipv4_result = ISC_R_SUCCESS;
+ }
+}
+
+void
+isc_net_enableipv6(void) {
+ initialize();
+ if (ipv6_result == ISC_R_DISABLED) {
+ ipv6_result = ISC_R_SUCCESS;
+ }
+}
diff --git a/lib/isc/netaddr.c b/lib/isc/netaddr.c
new file mode 100644
index 0000000..c674d83
--- /dev/null
+++ b/lib/isc/netaddr.c
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdio.h>
+
+#include <isc/buffer.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+bool
+isc_netaddr_equal(const isc_netaddr_t *a, const isc_netaddr_t *b) {
+ REQUIRE(a != NULL && b != NULL);
+
+ if (a->family != b->family) {
+ return (false);
+ }
+
+ if (a->zone != b->zone) {
+ return (false);
+ }
+
+ switch (a->family) {
+ case AF_INET:
+ if (a->type.in.s_addr != b->type.in.s_addr) {
+ return (false);
+ }
+ break;
+ case AF_INET6:
+ if (memcmp(&a->type.in6, &b->type.in6, sizeof(a->type.in6)) !=
+ 0 ||
+ a->zone != b->zone)
+ {
+ return (false);
+ }
+ break;
+ case AF_UNIX:
+ if (strcmp(a->type.un, b->type.un) != 0) {
+ return (false);
+ }
+ break;
+ default:
+ return (false);
+ }
+ return (true);
+}
+
+bool
+isc_netaddr_eqprefix(const isc_netaddr_t *a, const isc_netaddr_t *b,
+ unsigned int prefixlen) {
+ const unsigned char *pa = NULL, *pb = NULL;
+ unsigned int ipabytes = 0; /* Length of whole IP address in bytes */
+ unsigned int nbytes; /* Number of significant whole bytes */
+ unsigned int nbits; /* Number of significant leftover bits */
+
+ REQUIRE(a != NULL && b != NULL);
+
+ if (a->family != b->family) {
+ return (false);
+ }
+
+ if (a->zone != b->zone && b->zone != 0) {
+ return (false);
+ }
+
+ switch (a->family) {
+ case AF_INET:
+ pa = (const unsigned char *)&a->type.in;
+ pb = (const unsigned char *)&b->type.in;
+ ipabytes = 4;
+ break;
+ case AF_INET6:
+ pa = (const unsigned char *)&a->type.in6;
+ pb = (const unsigned char *)&b->type.in6;
+ ipabytes = 16;
+ break;
+ default:
+ return (false);
+ }
+
+ /*
+ * Don't crash if we get a pattern like 10.0.0.1/9999999.
+ */
+ if (prefixlen > ipabytes * 8) {
+ prefixlen = ipabytes * 8;
+ }
+
+ nbytes = prefixlen / 8;
+ nbits = prefixlen % 8;
+
+ if (nbytes > 0) {
+ if (memcmp(pa, pb, nbytes) != 0) {
+ return (false);
+ }
+ }
+ if (nbits > 0) {
+ unsigned int bytea, byteb, mask;
+ INSIST(nbytes < ipabytes);
+ INSIST(nbits < 8);
+ bytea = pa[nbytes];
+ byteb = pb[nbytes];
+ mask = (0xFF << (8 - nbits)) & 0xFF;
+ if ((bytea & mask) != (byteb & mask)) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+isc_result_t
+isc_netaddr_totext(const isc_netaddr_t *netaddr, isc_buffer_t *target) {
+ char abuf[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255")];
+ char zbuf[sizeof("%4294967295")];
+ unsigned int alen;
+ int zlen;
+ const char *r;
+ const void *type;
+
+ REQUIRE(netaddr != NULL);
+
+ switch (netaddr->family) {
+ case AF_INET:
+ type = &netaddr->type.in;
+ break;
+ case AF_INET6:
+ type = &netaddr->type.in6;
+ break;
+ case AF_UNIX:
+ alen = strlen(netaddr->type.un);
+ if (alen > isc_buffer_availablelength(target)) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putmem(target,
+ (const unsigned char *)(netaddr->type.un),
+ alen);
+ return (ISC_R_SUCCESS);
+ default:
+ return (ISC_R_FAILURE);
+ }
+ r = inet_ntop(netaddr->family, type, abuf, sizeof(abuf));
+ if (r == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ alen = strlen(abuf);
+ INSIST(alen < sizeof(abuf));
+
+ zlen = 0;
+ if (netaddr->family == AF_INET6 && netaddr->zone != 0) {
+ zlen = snprintf(zbuf, sizeof(zbuf), "%%%u", netaddr->zone);
+ if (zlen < 0) {
+ return (ISC_R_FAILURE);
+ }
+ INSIST((unsigned int)zlen < sizeof(zbuf));
+ }
+
+ if (alen + zlen > isc_buffer_availablelength(target)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putmem(target, (unsigned char *)abuf, alen);
+ isc_buffer_putmem(target, (unsigned char *)zbuf, (unsigned int)zlen);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_netaddr_format(const isc_netaddr_t *na, char *array, unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ isc_buffer_init(&buf, array, size);
+ result = isc_netaddr_totext(na, &buf);
+
+ if (size == 0) {
+ return;
+ }
+
+ /*
+ * 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) {
+ snprintf(array, size, "<unknown address, family %u>",
+ na->family);
+ array[size - 1] = '\0';
+ }
+}
+
+isc_result_t
+isc_netaddr_prefixok(const isc_netaddr_t *na, unsigned int prefixlen) {
+ static const unsigned char zeros[16];
+ unsigned int nbits, nbytes, ipbytes = 0;
+ const unsigned char *p;
+
+ switch (na->family) {
+ case AF_INET:
+ p = (const unsigned char *)&na->type.in;
+ ipbytes = 4;
+ if (prefixlen > 32) {
+ return (ISC_R_RANGE);
+ }
+ break;
+ case AF_INET6:
+ p = (const unsigned char *)&na->type.in6;
+ ipbytes = 16;
+ if (prefixlen > 128) {
+ return (ISC_R_RANGE);
+ }
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ nbytes = prefixlen / 8;
+ nbits = prefixlen % 8;
+ if (nbits != 0) {
+ INSIST(nbytes < ipbytes);
+ if ((p[nbytes] & (0xff >> nbits)) != 0U) {
+ return (ISC_R_FAILURE);
+ }
+ nbytes++;
+ }
+ if (nbytes < ipbytes &&
+ memcmp(p + nbytes, zeros, ipbytes - nbytes) != 0)
+ {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_netaddr_masktoprefixlen(const isc_netaddr_t *s, unsigned int *lenp) {
+ unsigned int nbits = 0, nbytes = 0, ipbytes = 0, i;
+ const unsigned char *p;
+
+ switch (s->family) {
+ case AF_INET:
+ p = (const unsigned char *)&s->type.in;
+ ipbytes = 4;
+ break;
+ case AF_INET6:
+ p = (const unsigned char *)&s->type.in6;
+ ipbytes = 16;
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+ for (i = 0; i < ipbytes; i++) {
+ if (p[i] != 0xFF) {
+ break;
+ }
+ }
+ nbytes = i;
+ if (i < ipbytes) {
+ unsigned int c = p[nbytes];
+ while ((c & 0x80) != 0 && nbits < 8) {
+ c <<= 1;
+ nbits++;
+ }
+ if ((c & 0xFF) != 0) {
+ return (ISC_R_MASKNONCONTIG);
+ }
+ i++;
+ }
+ for (; i < ipbytes; i++) {
+ if (p[i] != 0) {
+ return (ISC_R_MASKNONCONTIG);
+ }
+ }
+ *lenp = nbytes * 8 + nbits;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_netaddr_fromin(isc_netaddr_t *netaddr, const struct in_addr *ina) {
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_INET;
+ netaddr->type.in = *ina;
+}
+
+void
+isc_netaddr_fromin6(isc_netaddr_t *netaddr, const struct in6_addr *ina6) {
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_INET6;
+ netaddr->type.in6 = *ina6;
+}
+
+isc_result_t
+isc_netaddr_frompath(isc_netaddr_t *netaddr, const char *path) {
+ if (strlen(path) > sizeof(netaddr->type.un) - 1) {
+ return (ISC_R_NOSPACE);
+ }
+
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_UNIX;
+ strlcpy(netaddr->type.un, path, sizeof(netaddr->type.un));
+ netaddr->zone = 0;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_netaddr_setzone(isc_netaddr_t *netaddr, uint32_t zone) {
+ /* we currently only support AF_INET6. */
+ REQUIRE(netaddr->family == AF_INET6);
+
+ netaddr->zone = zone;
+}
+
+uint32_t
+isc_netaddr_getzone(const isc_netaddr_t *netaddr) {
+ return (netaddr->zone);
+}
+
+void
+isc_netaddr_fromsockaddr(isc_netaddr_t *t, const isc_sockaddr_t *s) {
+ int family = s->type.sa.sa_family;
+ t->family = family;
+ switch (family) {
+ case AF_INET:
+ t->type.in = s->type.sin.sin_addr;
+ t->zone = 0;
+ break;
+ case AF_INET6:
+ memmove(&t->type.in6, &s->type.sin6.sin6_addr, 16);
+ t->zone = s->type.sin6.sin6_scope_id;
+ break;
+ case AF_UNIX:
+ memmove(t->type.un, s->type.sunix.sun_path, sizeof(t->type.un));
+ t->zone = 0;
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_netaddr_any(isc_netaddr_t *netaddr) {
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_INET;
+ netaddr->type.in.s_addr = INADDR_ANY;
+}
+
+void
+isc_netaddr_any6(isc_netaddr_t *netaddr) {
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_INET6;
+ netaddr->type.in6 = in6addr_any;
+}
+
+void
+isc_netaddr_unspec(isc_netaddr_t *netaddr) {
+ memset(netaddr, 0, sizeof(*netaddr));
+ netaddr->family = AF_UNSPEC;
+}
+
+bool
+isc_netaddr_ismulticast(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (ISC_IPADDR_ISMULTICAST(na->type.in.s_addr));
+ case AF_INET6:
+ return (IN6_IS_ADDR_MULTICAST(&na->type.in6));
+ default:
+ return (false); /* XXXMLG ? */
+ }
+}
+
+bool
+isc_netaddr_isexperimental(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (ISC_IPADDR_ISEXPERIMENTAL(na->type.in.s_addr));
+ default:
+ return (false); /* XXXMLG ? */
+ }
+}
+
+bool
+isc_netaddr_islinklocal(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (false);
+ case AF_INET6:
+ return (IN6_IS_ADDR_LINKLOCAL(&na->type.in6));
+ default:
+ return (false);
+ }
+}
+
+bool
+isc_netaddr_issitelocal(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (false);
+ case AF_INET6:
+ return (IN6_IS_ADDR_SITELOCAL(&na->type.in6));
+ default:
+ return (false);
+ }
+}
+
+#define ISC_IPADDR_ISNETZERO(i) \
+ (((uint32_t)(i)&ISC__IPADDR(0xff000000)) == ISC__IPADDR(0x00000000))
+
+bool
+isc_netaddr_isnetzero(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (ISC_IPADDR_ISNETZERO(na->type.in.s_addr));
+ case AF_INET6:
+ return (false);
+ default:
+ return (false);
+ }
+}
+
+void
+isc_netaddr_fromv4mapped(isc_netaddr_t *t, const isc_netaddr_t *s) {
+ isc_netaddr_t *src;
+
+ DE_CONST(s, src); /* Must come before IN6_IS_ADDR_V4MAPPED. */
+
+ REQUIRE(s->family == AF_INET6);
+ REQUIRE(IN6_IS_ADDR_V4MAPPED(&src->type.in6));
+
+ memset(t, 0, sizeof(*t));
+ t->family = AF_INET;
+ memmove(&t->type.in, (char *)&src->type.in6 + 12, 4);
+ return;
+}
+
+bool
+isc_netaddr_isloopback(const isc_netaddr_t *na) {
+ switch (na->family) {
+ case AF_INET:
+ return (((ntohl(na->type.in.s_addr) & 0xff000000U) ==
+ 0x7f000000U));
+ case AF_INET6:
+ return (IN6_IS_ADDR_LOOPBACK(&na->type.in6));
+ default:
+ return (false);
+ }
+}
diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c
new file mode 100644
index 0000000..f2d3e2d
--- /dev/null
+++ b/lib/isc/netmgr/http.c
@@ -0,0 +1,3755 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <nghttp2/nghttp2.h>
+#include <signal.h>
+#include <string.h>
+
+#include <isc/base64.h>
+#include <isc/log.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/tls.h>
+#include <isc/url.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+
+#define AUTHEXTRA 7
+
+#define MAX_DNS_MESSAGE_SIZE (UINT16_MAX)
+
+#define DNS_MEDIA_TYPE "application/dns-message"
+
+/*
+ * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
+ * for additional details. Basically it means "avoid caching by any
+ * means."
+ */
+#define DEFAULT_CACHE_CONTROL "no-cache, no-store, must-revalidate"
+
+/*
+ * If server during request processing surpasses any of the limits
+ * below, it will just reset the stream without returning any error
+ * codes in a response. Ideally, these parameters should be
+ * configurable both globally and per every HTTP endpoint description
+ * in the configuration file, but for now it should be enough.
+ */
+
+/*
+ * 128K should be enough to encode 64K of data into base64url inside GET
+ * request and have extra space for other headers
+ */
+#define MAX_ALLOWED_DATA_IN_HEADERS (MAX_DNS_MESSAGE_SIZE * 2)
+
+#define MAX_ALLOWED_DATA_IN_POST \
+ (MAX_DNS_MESSAGE_SIZE + MAX_DNS_MESSAGE_SIZE / 2)
+
+#define HEADER_MATCH(header, name, namelen) \
+ (((namelen) == sizeof(header) - 1) && \
+ (strncasecmp((header), (const char *)(name), (namelen)) == 0))
+
+#define MIN_SUCCESSFUL_HTTP_STATUS (200)
+#define MAX_SUCCESSFUL_HTTP_STATUS (299)
+
+/* This definition sets the upper limit of pending write buffer to an
+ * adequate enough value. That is done mostly to fight a limitation
+ * for a max TLS record size in flamethrower (2K). In a perfect world
+ * this constant should not be required, if we ever move closer to
+ * that state, the constant, and corresponding code, should be
+ * removed. For now the limit seems adequate enough to fight
+ * "tinygrams" problem. */
+#define FLUSH_HTTP_WRITE_BUFFER_AFTER (1536)
+
+/* This switch is here mostly to test the code interoperability with
+ * buggy implementations */
+#define ENABLE_HTTP_WRITE_BUFFERING 1
+
+#define SUCCESSFUL_HTTP_STATUS(code) \
+ ((code) >= MIN_SUCCESSFUL_HTTP_STATUS && \
+ (code) <= MAX_SUCCESSFUL_HTTP_STATUS)
+
+#define INITIAL_DNS_MESSAGE_BUFFER_SIZE (512)
+
+typedef struct isc_nm_http_response_status {
+ size_t code;
+ size_t content_length;
+ bool content_type_valid;
+} isc_nm_http_response_status_t;
+
+typedef struct http_cstream {
+ isc_nm_recv_cb_t read_cb;
+ void *read_cbarg;
+ isc_nm_cb_t connect_cb;
+ void *connect_cbarg;
+
+ bool sending;
+ bool reading;
+
+ char *uri;
+ isc_url_parser_t up;
+
+ char *authority;
+ size_t authoritylen;
+ char *path;
+
+ isc_buffer_t *rbuf;
+
+ size_t pathlen;
+ int32_t stream_id;
+
+ bool post; /* POST or GET */
+ isc_buffer_t *postdata;
+ char *GET_path;
+ size_t GET_path_len;
+
+ isc_nm_http_response_status_t response_status;
+ isc_nmsocket_t *httpsock;
+ LINK(struct http_cstream) link;
+} http_cstream_t;
+
+#define HTTP2_SESSION_MAGIC ISC_MAGIC('H', '2', 'S', 'S')
+#define VALID_HTTP2_SESSION(t) ISC_MAGIC_VALID(t, HTTP2_SESSION_MAGIC)
+
+typedef ISC_LIST(isc__nm_uvreq_t) isc__nm_http_pending_callbacks_t;
+
+struct isc_nm_http_session {
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+
+ size_t sending;
+ bool reading;
+ bool closed;
+ bool closing;
+
+ nghttp2_session *ngsession;
+ bool client;
+
+ ISC_LIST(http_cstream_t) cstreams;
+ ISC_LIST(isc_nmsocket_h2_t) sstreams;
+ size_t nsstreams;
+
+ isc_nmhandle_t *handle;
+ isc_nmhandle_t *client_httphandle;
+ isc_nmsocket_t *serversocket;
+
+ isc_buffer_t *buf;
+
+ isc_tlsctx_t *tlsctx;
+ uint32_t max_concurrent_streams;
+
+ isc__nm_http_pending_callbacks_t pending_write_callbacks;
+ isc_buffer_t *pending_write_data;
+};
+
+typedef enum isc_http_error_responses {
+ ISC_HTTP_ERROR_SUCCESS, /* 200 */
+ ISC_HTTP_ERROR_NOT_FOUND, /* 404 */
+ ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, /* 413 */
+ ISC_HTTP_ERROR_URI_TOO_LONG, /* 414 */
+ ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, /* 415 */
+ ISC_HTTP_ERROR_BAD_REQUEST, /* 400 */
+ ISC_HTTP_ERROR_NOT_IMPLEMENTED, /* 501 */
+ ISC_HTTP_ERROR_GENERIC, /* 500 Internal Server Error */
+ ISC_HTTP_ERROR_MAX
+} isc_http_error_responses_t;
+
+typedef struct isc_http_send_req {
+ isc_nm_http_session_t *session;
+ isc_nmhandle_t *transphandle;
+ isc_nmhandle_t *httphandle;
+ isc_nm_cb_t cb;
+ void *cbarg;
+ isc_buffer_t *pending_write_data;
+ isc__nm_http_pending_callbacks_t pending_write_callbacks;
+} isc_http_send_req_t;
+
+#define HTTP_ENDPOINTS_MAGIC ISC_MAGIC('H', 'T', 'E', 'P')
+#define VALID_HTTP_ENDPOINTS(t) ISC_MAGIC_VALID(t, HTTP_ENDPOINTS_MAGIC)
+
+static bool
+http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle,
+ isc_nm_cb_t cb, void *cbarg);
+
+static void
+http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle,
+ isc_nm_cb_t send_cb, void *send_cbarg);
+
+static void
+failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+ isc_nm_http_session_t *session);
+
+static void
+client_call_failed_read_cb(isc_result_t result, isc_nm_http_session_t *session);
+
+static void
+server_call_failed_read_cb(isc_result_t result, isc_nm_http_session_t *session);
+
+static void
+failed_read_cb(isc_result_t result, isc_nm_http_session_t *session);
+
+static isc_result_t
+server_send_error_response(const isc_http_error_responses_t error,
+ nghttp2_session *ngsession, isc_nmsocket_t *socket);
+
+static isc_result_t
+client_send(isc_nmhandle_t *handle, const isc_region_t *region);
+
+static void
+finish_http_session(isc_nm_http_session_t *session);
+
+static void
+http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle);
+
+static void
+call_pending_callbacks(isc__nm_http_pending_callbacks_t pending_callbacks,
+ isc_result_t result);
+
+static void
+server_call_cb(isc_nmsocket_t *socket, isc_nm_http_session_t *session,
+ const isc_result_t result, isc_region_t *data);
+
+static isc_nm_httphandler_t *
+http_endpoints_find(const char *request_path,
+ const isc_nm_http_endpoints_t *restrict eps);
+
+static void
+http_init_listener_endpoints(isc_nmsocket_t *listener,
+ isc_nm_http_endpoints_t *epset);
+
+static void
+http_cleanup_listener_endpoints(isc_nmsocket_t *listener);
+
+static isc_nm_http_endpoints_t *
+http_get_listener_endpoints(isc_nmsocket_t *listener, const int tid);
+
+static bool
+http_session_active(isc_nm_http_session_t *session) {
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ return (!session->closed && !session->closing);
+}
+
+static void *
+http_malloc(size_t sz, isc_mem_t *mctx) {
+ return (isc_mem_allocate(mctx, sz));
+}
+
+static void *
+http_calloc(size_t n, size_t sz, isc_mem_t *mctx) {
+ const size_t msize = n * sz;
+ void *data = isc_mem_allocate(mctx, msize);
+
+ memset(data, 0, msize);
+ return (data);
+}
+
+static void *
+http_realloc(void *p, size_t newsz, isc_mem_t *mctx) {
+ return (isc_mem_reallocate(mctx, p, newsz));
+}
+
+static void
+http_free(void *p, isc_mem_t *mctx) {
+ if (p == NULL) { /* as standard free() behaves */
+ return;
+ }
+ isc_mem_free(mctx, p);
+}
+
+static void
+init_nghttp2_mem(isc_mem_t *mctx, nghttp2_mem *mem) {
+ *mem = (nghttp2_mem){ .malloc = (nghttp2_malloc)http_malloc,
+ .calloc = (nghttp2_calloc)http_calloc,
+ .realloc = (nghttp2_realloc)http_realloc,
+ .free = (nghttp2_free)http_free,
+ .mem_user_data = mctx };
+}
+
+static void
+new_session(isc_mem_t *mctx, isc_tlsctx_t *tctx,
+ isc_nm_http_session_t **sessionp) {
+ isc_nm_http_session_t *session = NULL;
+
+ REQUIRE(sessionp != NULL && *sessionp == NULL);
+ REQUIRE(mctx != NULL);
+
+ session = isc_mem_get(mctx, sizeof(isc_nm_http_session_t));
+ *session = (isc_nm_http_session_t){ .magic = HTTP2_SESSION_MAGIC,
+ .tlsctx = tctx };
+ isc_refcount_init(&session->references, 1);
+ isc_mem_attach(mctx, &session->mctx);
+ ISC_LIST_INIT(session->cstreams);
+ ISC_LIST_INIT(session->sstreams);
+ ISC_LIST_INIT(session->pending_write_callbacks);
+
+ *sessionp = session;
+}
+
+void
+isc__nm_httpsession_attach(isc_nm_http_session_t *source,
+ isc_nm_http_session_t **targetp) {
+ REQUIRE(VALID_HTTP2_SESSION(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp) {
+ isc_nm_http_session_t *session = NULL;
+
+ REQUIRE(sessionp != NULL);
+
+ session = *sessionp;
+ *sessionp = NULL;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+
+ if (isc_refcount_decrement(&session->references) > 1) {
+ return;
+ }
+
+ finish_http_session(session);
+
+ INSIST(ISC_LIST_EMPTY(session->sstreams));
+ INSIST(ISC_LIST_EMPTY(session->cstreams));
+
+ if (session->ngsession != NULL) {
+ nghttp2_session_del(session->ngsession);
+ session->ngsession = NULL;
+ }
+
+ if (session->buf != NULL) {
+ isc_buffer_free(&session->buf);
+ }
+
+ /* We need an acquire memory barrier here */
+ (void)isc_refcount_current(&session->references);
+
+ session->magic = 0;
+ isc_mem_putanddetach(&session->mctx, session,
+ sizeof(isc_nm_http_session_t));
+}
+
+static http_cstream_t *
+find_http_cstream(int32_t stream_id, isc_nm_http_session_t *session) {
+ http_cstream_t *cstream = NULL;
+ REQUIRE(VALID_HTTP2_SESSION(session));
+
+ if (ISC_LIST_EMPTY(session->cstreams)) {
+ return (NULL);
+ }
+
+ for (cstream = ISC_LIST_HEAD(session->cstreams); cstream != NULL;
+ cstream = ISC_LIST_NEXT(cstream, link))
+ {
+ if (cstream->stream_id == stream_id) {
+ break;
+ }
+ }
+
+ /* LRU-like behaviour */
+ if (cstream && ISC_LIST_HEAD(session->cstreams) != cstream) {
+ ISC_LIST_UNLINK(session->cstreams, cstream, link);
+ ISC_LIST_PREPEND(session->cstreams, cstream, link);
+ }
+
+ return (cstream);
+}
+
+static isc_result_t
+new_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) {
+ isc_mem_t *mctx = sock->mgr->mctx;
+ const char *uri = NULL;
+ bool post;
+ http_cstream_t *stream = NULL;
+ isc_result_t result;
+
+ uri = sock->h2.session->handle->sock->h2.connect.uri;
+ post = sock->h2.session->handle->sock->h2.connect.post;
+
+ stream = isc_mem_get(mctx, sizeof(http_cstream_t));
+ *stream = (http_cstream_t){ .stream_id = -1,
+ .post = post,
+ .uri = isc_mem_strdup(mctx, uri) };
+ ISC_LINK_INIT(stream, link);
+
+ result = isc_url_parse(stream->uri, strlen(stream->uri), 0,
+ &stream->up);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_free(mctx, stream->uri);
+ isc_mem_put(mctx, stream, sizeof(http_cstream_t));
+ return (result);
+ }
+
+ isc__nmsocket_attach(sock, &stream->httpsock);
+ stream->authoritylen = stream->up.field_data[ISC_UF_HOST].len;
+ stream->authority = isc_mem_get(mctx, stream->authoritylen + AUTHEXTRA);
+ memmove(stream->authority, &uri[stream->up.field_data[ISC_UF_HOST].off],
+ stream->up.field_data[ISC_UF_HOST].len);
+
+ if (stream->up.field_set & (1 << ISC_UF_PORT)) {
+ stream->authoritylen += (size_t)snprintf(
+ stream->authority +
+ stream->up.field_data[ISC_UF_HOST].len,
+ AUTHEXTRA, ":%u", stream->up.port);
+ }
+
+ /* If we don't have path in URI, we use "/" as path. */
+ stream->pathlen = 1;
+ if (stream->up.field_set & (1 << ISC_UF_PATH)) {
+ stream->pathlen = stream->up.field_data[ISC_UF_PATH].len;
+ }
+ if (stream->up.field_set & (1 << ISC_UF_QUERY)) {
+ /* +1 for '?' character */
+ stream->pathlen +=
+ (size_t)(stream->up.field_data[ISC_UF_QUERY].len + 1);
+ }
+
+ stream->path = isc_mem_get(mctx, stream->pathlen);
+ if (stream->up.field_set & (1 << ISC_UF_PATH)) {
+ memmove(stream->path,
+ &uri[stream->up.field_data[ISC_UF_PATH].off],
+ stream->up.field_data[ISC_UF_PATH].len);
+ } else {
+ stream->path[0] = '/';
+ }
+
+ if (stream->up.field_set & (1 << ISC_UF_QUERY)) {
+ stream->path[stream->pathlen -
+ stream->up.field_data[ISC_UF_QUERY].len - 1] = '?';
+ memmove(stream->path + stream->pathlen -
+ stream->up.field_data[ISC_UF_QUERY].len,
+ &uri[stream->up.field_data[ISC_UF_QUERY].off],
+ stream->up.field_data[ISC_UF_QUERY].len);
+ }
+
+ isc_buffer_allocate(mctx, &stream->rbuf,
+ INITIAL_DNS_MESSAGE_BUFFER_SIZE);
+ isc_buffer_setautorealloc(stream->rbuf, true);
+
+ ISC_LIST_PREPEND(sock->h2.session->cstreams, stream, link);
+ *streamp = stream;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+put_http_cstream(isc_mem_t *mctx, http_cstream_t *stream) {
+ isc_mem_put(mctx, stream->path, stream->pathlen);
+ isc_mem_put(mctx, stream->authority,
+ stream->up.field_data[ISC_UF_HOST].len + AUTHEXTRA);
+ isc_mem_free(mctx, stream->uri);
+ if (stream->GET_path != NULL) {
+ isc_mem_free(mctx, stream->GET_path);
+ stream->GET_path = NULL;
+ stream->GET_path_len = 0;
+ }
+
+ if (stream->postdata != NULL) {
+ INSIST(stream->post);
+ isc_buffer_free(&stream->postdata);
+ }
+
+ if (stream == stream->httpsock->h2.connect.cstream) {
+ stream->httpsock->h2.connect.cstream = NULL;
+ }
+ if (ISC_LINK_LINKED(stream, link)) {
+ ISC_LIST_UNLINK(stream->httpsock->h2.session->cstreams, stream,
+ link);
+ }
+ isc__nmsocket_detach(&stream->httpsock);
+
+ isc_buffer_free(&stream->rbuf);
+ isc_mem_put(mctx, stream, sizeof(http_cstream_t));
+}
+
+static void
+finish_http_session(isc_nm_http_session_t *session) {
+ if (session->closed) {
+ return;
+ }
+
+ if (session->handle != NULL) {
+ if (!session->closed) {
+ session->closed = true;
+ isc_nm_cancelread(session->handle);
+ }
+
+ if (session->client) {
+ client_call_failed_read_cb(ISC_R_UNEXPECTED, session);
+ } else {
+ server_call_failed_read_cb(ISC_R_UNEXPECTED, session);
+ }
+
+ call_pending_callbacks(session->pending_write_callbacks,
+ ISC_R_UNEXPECTED);
+ ISC_LIST_INIT(session->pending_write_callbacks);
+
+ if (session->pending_write_data != NULL) {
+ isc_buffer_free(&session->pending_write_data);
+ }
+
+ isc_nmhandle_detach(&session->handle);
+ }
+
+ if (session->client_httphandle != NULL) {
+ isc_nmhandle_detach(&session->client_httphandle);
+ }
+
+ INSIST(ISC_LIST_EMPTY(session->cstreams));
+
+ /* detach from server socket */
+ if (session->serversocket != NULL) {
+ isc__nmsocket_detach(&session->serversocket);
+ }
+ session->closed = true;
+}
+
+static int
+on_client_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data,
+ size_t len, isc_nm_http_session_t *session) {
+ http_cstream_t *cstream = find_http_cstream(stream_id, session);
+
+ if (cstream != NULL) {
+ size_t new_rbufsize = len;
+ INSIST(cstream->rbuf != NULL);
+ new_rbufsize += isc_buffer_usedlength(cstream->rbuf);
+ if (new_rbufsize <= MAX_DNS_MESSAGE_SIZE &&
+ new_rbufsize <= cstream->response_status.content_length)
+ {
+ isc_buffer_putmem(cstream->rbuf, data, len);
+ } else {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+ } else {
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ return (0);
+}
+
+static int
+on_server_data_chunk_recv_callback(int32_t stream_id, const uint8_t *data,
+ size_t len, isc_nm_http_session_t *session) {
+ isc_nmsocket_h2_t *h2 = ISC_LIST_HEAD(session->sstreams);
+ while (h2 != NULL) {
+ if (stream_id == h2->stream_id) {
+ if (isc_buffer_base(&h2->rbuf) == NULL) {
+ isc_buffer_init(
+ &h2->rbuf,
+ isc_mem_allocate(session->mctx,
+ h2->content_length),
+ MAX_DNS_MESSAGE_SIZE);
+ }
+ size_t new_bufsize = isc_buffer_usedlength(&h2->rbuf) +
+ len;
+ if (new_bufsize <= MAX_DNS_MESSAGE_SIZE &&
+ new_bufsize <= h2->content_length)
+ {
+ isc_buffer_putmem(&h2->rbuf, data, len);
+ break;
+ }
+
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+ h2 = ISC_LIST_NEXT(h2, link);
+ }
+ if (h2 == NULL) {
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ return (0);
+}
+
+static int
+on_data_chunk_recv_callback(nghttp2_session *ngsession, uint8_t flags,
+ int32_t stream_id, const uint8_t *data, size_t len,
+ void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ int rv;
+
+ UNUSED(ngsession);
+ UNUSED(flags);
+
+ if (session->client) {
+ rv = on_client_data_chunk_recv_callback(stream_id, data, len,
+ session);
+ } else {
+ rv = on_server_data_chunk_recv_callback(stream_id, data, len,
+ session);
+ }
+
+ return (rv);
+}
+
+static void
+call_unlink_cstream_readcb(http_cstream_t *cstream,
+ isc_nm_http_session_t *session,
+ isc_result_t result) {
+ isc_region_t read_data;
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(cstream != NULL);
+ ISC_LIST_UNLINK(session->cstreams, cstream, link);
+ INSIST(VALID_NMHANDLE(session->client_httphandle));
+ isc_buffer_usedregion(cstream->rbuf, &read_data);
+ cstream->read_cb(session->client_httphandle, result, &read_data,
+ cstream->read_cbarg);
+ put_http_cstream(session->mctx, cstream);
+}
+
+static int
+on_client_stream_close_callback(int32_t stream_id,
+ isc_nm_http_session_t *session) {
+ http_cstream_t *cstream = find_http_cstream(stream_id, session);
+
+ if (cstream != NULL) {
+ isc_result_t result =
+ SUCCESSFUL_HTTP_STATUS(cstream->response_status.code)
+ ? ISC_R_SUCCESS
+ : ISC_R_FAILURE;
+ call_unlink_cstream_readcb(cstream, session, result);
+ if (ISC_LIST_EMPTY(session->cstreams)) {
+ int rv = 0;
+ rv = nghttp2_session_terminate_session(
+ session->ngsession, NGHTTP2_NO_ERROR);
+ if (rv != 0) {
+ return (rv);
+ }
+ /* Mark the session as closing one to finish it on a
+ * subsequent call to http_do_bio() */
+ session->closing = true;
+ }
+ } else {
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ return (0);
+}
+
+static int
+on_server_stream_close_callback(int32_t stream_id,
+ isc_nm_http_session_t *session) {
+ isc_nmsocket_t *sock = nghttp2_session_get_stream_user_data(
+ session->ngsession, stream_id);
+ int rv = 0;
+
+ ISC_LIST_UNLINK(session->sstreams, &sock->h2, link);
+ session->nsstreams--;
+
+ /*
+ * By making a call to isc__nmsocket_prep_destroy(), we ensure that
+ * the socket gets marked as inactive, allowing the HTTP/2 data
+ * associated with it to be properly disposed of eventually.
+ *
+ * An HTTP/2 stream socket will normally be marked as inactive in
+ * the normal course of operation. However, when browsers terminate
+ * HTTP/2 streams prematurely (e.g. by sending RST_STREAM),
+ * corresponding sockets can remain marked as active, retaining
+ * references to the HTTP/2 data (most notably the session objects),
+ * preventing them from being correctly freed and leading to BIND
+ * hanging on shutdown. Calling isc__nmsocket_prep_destroy()
+ * ensures that this will not happen.
+ */
+ isc__nmsocket_prep_destroy(sock);
+ isc__nmsocket_detach(&sock);
+ return (rv);
+}
+
+static int
+on_stream_close_callback(nghttp2_session *ngsession, int32_t stream_id,
+ uint32_t error_code, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ int rv = 0;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(session->ngsession == ngsession);
+
+ UNUSED(error_code);
+
+ if (session->client) {
+ rv = on_client_stream_close_callback(stream_id, session);
+ } else {
+ rv = on_server_stream_close_callback(stream_id, session);
+ }
+
+ return (rv);
+}
+
+static bool
+client_handle_status_header(http_cstream_t *cstream, const uint8_t *value,
+ const size_t valuelen) {
+ char tmp[32] = { 0 };
+ const size_t tmplen = sizeof(tmp) - 1;
+
+ strncpy(tmp, (const char *)value, ISC_MIN(tmplen, valuelen));
+ cstream->response_status.code = strtoul(tmp, NULL, 10);
+
+ if (SUCCESSFUL_HTTP_STATUS(cstream->response_status.code)) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool
+client_handle_content_length_header(http_cstream_t *cstream,
+ const uint8_t *value,
+ const size_t valuelen) {
+ char tmp[32] = { 0 };
+ const size_t tmplen = sizeof(tmp) - 1;
+
+ strncpy(tmp, (const char *)value, ISC_MIN(tmplen, valuelen));
+ cstream->response_status.content_length = strtoul(tmp, NULL, 10);
+
+ if (cstream->response_status.content_length == 0 ||
+ cstream->response_status.content_length > MAX_DNS_MESSAGE_SIZE)
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool
+client_handle_content_type_header(http_cstream_t *cstream, const uint8_t *value,
+ const size_t valuelen) {
+ const char type_dns_message[] = DNS_MEDIA_TYPE;
+ const size_t len = sizeof(type_dns_message) - 1;
+
+ UNUSED(valuelen);
+
+ if (strncasecmp((const char *)value, type_dns_message, len) == 0) {
+ cstream->response_status.content_type_valid = true;
+ return (true);
+ }
+
+ return (false);
+}
+
+static int
+client_on_header_callback(nghttp2_session *ngsession,
+ const nghttp2_frame *frame, const uint8_t *name,
+ size_t namelen, const uint8_t *value, size_t valuelen,
+ uint8_t flags, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ http_cstream_t *cstream = NULL;
+ const char status[] = ":status";
+ const char content_length[] = "Content-Length";
+ const char content_type[] = "Content-Type";
+ bool header_ok = true;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(session->client);
+
+ UNUSED(flags);
+ UNUSED(ngsession);
+
+ cstream = find_http_cstream(frame->hd.stream_id, session);
+ if (cstream == NULL) {
+ /*
+ * This could happen in two cases:
+ * - the server sent us bad data, or
+ * - we closed the session prematurely before receiving all
+ * responses (i.e., because of a belated or partial response).
+ */
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ INSIST(!ISC_LIST_EMPTY(session->cstreams));
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat != NGHTTP2_HCAT_RESPONSE) {
+ break;
+ }
+
+ if (HEADER_MATCH(status, name, namelen)) {
+ header_ok = client_handle_status_header(cstream, value,
+ valuelen);
+ } else if (HEADER_MATCH(content_length, name, namelen)) {
+ header_ok = client_handle_content_length_header(
+ cstream, value, valuelen);
+ } else if (HEADER_MATCH(content_type, name, namelen)) {
+ header_ok = client_handle_content_type_header(
+ cstream, value, valuelen);
+ }
+ break;
+ }
+
+ if (!header_ok) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+
+ return (0);
+}
+
+static void
+initialize_nghttp2_client_session(isc_nm_http_session_t *session) {
+ nghttp2_session_callbacks *callbacks = NULL;
+ nghttp2_option *option = NULL;
+ nghttp2_mem mem;
+
+ init_nghttp2_mem(session->mctx, &mem);
+ RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
+ RUNTIME_CHECK(nghttp2_option_new(&option) == 0);
+
+#if NGHTTP2_VERSION_NUM >= (0x010c00)
+ nghttp2_option_set_max_send_header_block_length(
+ option, MAX_ALLOWED_DATA_IN_HEADERS);
+#endif
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(
+ callbacks, client_on_header_callback);
+
+ RUNTIME_CHECK(nghttp2_session_client_new3(&session->ngsession,
+ callbacks, session, option,
+ &mem) == 0);
+
+ nghttp2_option_del(option);
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+static bool
+send_client_connection_header(isc_nm_http_session_t *session) {
+ nghttp2_settings_entry iv[] = { { NGHTTP2_SETTINGS_ENABLE_PUSH, 0 } };
+ int rv;
+
+ rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv,
+ sizeof(iv) / sizeof(iv[0]));
+ if (rv != 0) {
+ return (false);
+ }
+
+ return (true);
+}
+
+#define MAKE_NV(NAME, VALUE, VALUELEN) \
+ { \
+ (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \
+ sizeof(NAME) - 1, VALUELEN, NGHTTP2_NV_FLAG_NONE \
+ }
+
+#define MAKE_NV2(NAME, VALUE) \
+ { \
+ (uint8_t *)(uintptr_t)(NAME), (uint8_t *)(uintptr_t)(VALUE), \
+ sizeof(NAME) - 1, sizeof(VALUE) - 1, \
+ NGHTTP2_NV_FLAG_NONE \
+ }
+
+static ssize_t
+client_read_callback(nghttp2_session *ngsession, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ http_cstream_t *cstream = NULL;
+
+ REQUIRE(session->client);
+ REQUIRE(!ISC_LIST_EMPTY(session->cstreams));
+
+ UNUSED(ngsession);
+ UNUSED(source);
+
+ cstream = find_http_cstream(stream_id, session);
+ if (!cstream || cstream->stream_id != stream_id) {
+ /* We haven't found the stream, so we are not reading */
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ if (cstream->post) {
+ size_t len = isc_buffer_remaininglength(cstream->postdata);
+
+ if (len > length) {
+ len = length;
+ }
+
+ if (len > 0) {
+ memmove(buf, isc_buffer_current(cstream->postdata),
+ len);
+ isc_buffer_forward(cstream->postdata, len);
+ }
+
+ if (isc_buffer_remaininglength(cstream->postdata) == 0) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+
+ return (len);
+ } else {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ return (0);
+ }
+
+ return (0);
+}
+
+/*
+ * Send HTTP request to the remote peer.
+ */
+static isc_result_t
+client_submit_request(isc_nm_http_session_t *session, http_cstream_t *stream) {
+ int32_t stream_id;
+ char *uri = stream->uri;
+ isc_url_parser_t *up = &stream->up;
+ nghttp2_data_provider dp;
+
+ if (stream->post) {
+ char p[64];
+ snprintf(p, sizeof(p), "%u",
+ isc_buffer_usedlength(stream->postdata));
+ nghttp2_nv hdrs[] = {
+ MAKE_NV2(":method", "POST"),
+ MAKE_NV(":scheme",
+ &uri[up->field_data[ISC_UF_SCHEMA].off],
+ up->field_data[ISC_UF_SCHEMA].len),
+ MAKE_NV(":authority", stream->authority,
+ stream->authoritylen),
+ MAKE_NV(":path", stream->path, stream->pathlen),
+ MAKE_NV2("content-type", DNS_MEDIA_TYPE),
+ MAKE_NV2("accept", DNS_MEDIA_TYPE),
+ MAKE_NV("content-length", p, strlen(p)),
+ MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
+ };
+
+ dp = (nghttp2_data_provider){ .read_callback =
+ client_read_callback };
+ stream_id = nghttp2_submit_request(
+ session->ngsession, NULL, hdrs,
+ sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream);
+ } else {
+ INSIST(stream->GET_path != NULL);
+ INSIST(stream->GET_path_len != 0);
+ nghttp2_nv hdrs[] = {
+ MAKE_NV2(":method", "GET"),
+ MAKE_NV(":scheme",
+ &uri[up->field_data[ISC_UF_SCHEMA].off],
+ up->field_data[ISC_UF_SCHEMA].len),
+ MAKE_NV(":authority", stream->authority,
+ stream->authoritylen),
+ MAKE_NV(":path", stream->GET_path,
+ stream->GET_path_len),
+ MAKE_NV2("accept", DNS_MEDIA_TYPE),
+ MAKE_NV2("cache-control", DEFAULT_CACHE_CONTROL)
+ };
+
+ dp = (nghttp2_data_provider){ .read_callback =
+ client_read_callback };
+ stream_id = nghttp2_submit_request(
+ session->ngsession, NULL, hdrs,
+ sizeof(hdrs) / sizeof(hdrs[0]), &dp, stream);
+ }
+ if (stream_id < 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ stream->stream_id = stream_id;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Read callback from TLS socket.
+ */
+static void
+http_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+ void *data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)data;
+ ssize_t readlen;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+
+ UNUSED(handle);
+
+ if (result != ISC_R_SUCCESS) {
+ if (result != ISC_R_TIMEDOUT) {
+ session->reading = false;
+ }
+ failed_read_cb(result, session);
+ return;
+ }
+
+ readlen = nghttp2_session_mem_recv(session->ngsession, region->base,
+ region->length);
+ if (readlen < 0) {
+ failed_read_cb(ISC_R_UNEXPECTED, session);
+ return;
+ }
+
+ if ((size_t)readlen < region->length) {
+ size_t unread_size = region->length - readlen;
+ if (session->buf == NULL) {
+ isc_buffer_allocate(session->mctx, &session->buf,
+ unread_size);
+ isc_buffer_setautorealloc(session->buf, true);
+ }
+ isc_buffer_putmem(session->buf, region->base + readlen,
+ unread_size);
+ isc_nm_pauseread(session->handle);
+ }
+
+ /* We might have something to receive or send, do IO */
+ http_do_bio(session, NULL, NULL, NULL);
+}
+
+static void
+call_pending_callbacks(isc__nm_http_pending_callbacks_t pending_callbacks,
+ isc_result_t result) {
+ isc__nm_uvreq_t *cbreq = ISC_LIST_HEAD(pending_callbacks);
+ while (cbreq != NULL) {
+ isc__nm_uvreq_t *next = ISC_LIST_NEXT(cbreq, link);
+ ISC_LIST_UNLINK(pending_callbacks, cbreq, link);
+ isc__nm_sendcb(cbreq->handle->sock, cbreq, result, false);
+ cbreq = next;
+ }
+}
+
+static void
+http_writecb(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+ isc_http_send_req_t *req = (isc_http_send_req_t *)arg;
+ isc_nm_http_session_t *session = req->session;
+ isc_nmhandle_t *transphandle = req->transphandle;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ if (http_session_active(session)) {
+ INSIST(session->handle == handle);
+ }
+
+ call_pending_callbacks(req->pending_write_callbacks, result);
+
+ if (req->cb != NULL) {
+ req->cb(req->httphandle, result, req->cbarg);
+ isc_nmhandle_detach(&req->httphandle);
+ }
+
+ isc_buffer_free(&req->pending_write_data);
+ isc_mem_put(session->mctx, req, sizeof(*req));
+
+ session->sending--;
+ http_do_bio(session, NULL, NULL, NULL);
+ isc_nmhandle_detach(&transphandle);
+ if (result != ISC_R_SUCCESS && session->sending == 0) {
+ finish_http_session(session);
+ }
+ isc__nm_httpsession_detach(&session);
+}
+
+static void
+move_pending_send_callbacks(isc_nm_http_session_t *session,
+ isc_http_send_req_t *send) {
+ STATIC_ASSERT(
+ sizeof(session->pending_write_callbacks) ==
+ sizeof(send->pending_write_callbacks),
+ "size of pending writes requests callbacks lists differs");
+ memmove(&send->pending_write_callbacks,
+ &session->pending_write_callbacks,
+ sizeof(session->pending_write_callbacks));
+ ISC_LIST_INIT(session->pending_write_callbacks);
+}
+
+static bool
+http_send_outgoing(isc_nm_http_session_t *session, isc_nmhandle_t *httphandle,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc_http_send_req_t *send = NULL;
+ size_t total = 0;
+ isc_region_t send_data = { 0 };
+ isc_nmhandle_t *transphandle = NULL;
+#ifdef ENABLE_HTTP_WRITE_BUFFERING
+ size_t max_total_write_size = 0;
+#endif /* ENABLE_HTTP_WRITE_BUFFERING */
+
+ if (!http_session_active(session) ||
+ (!nghttp2_session_want_write(session->ngsession) &&
+ session->pending_write_data == NULL))
+ {
+ return (false);
+ }
+
+ /* We need to attach to the session->handle earlier because as an
+ * indirect result of the nghttp2_session_mem_send() the session
+ * might get closed and the handle detached. However, there is
+ * still some outgoing data to handle and we need to call it
+ * anyway if only to get the write callback passed here to get
+ * called properly. */
+ isc_nmhandle_attach(session->handle, &transphandle);
+
+ while (nghttp2_session_want_write(session->ngsession)) {
+ const uint8_t *data = NULL;
+ const size_t pending =
+ nghttp2_session_mem_send(session->ngsession, &data);
+ const size_t new_total = total + pending;
+
+ /* Sometimes nghttp2_session_mem_send() does not return any
+ * data to send even though nghttp2_session_want_write()
+ * returns success. */
+ if (pending == 0 || data == NULL) {
+ break;
+ }
+
+ /* reallocate buffer if required */
+ if (session->pending_write_data == NULL) {
+ isc_buffer_allocate(session->mctx,
+ &session->pending_write_data,
+ INITIAL_DNS_MESSAGE_BUFFER_SIZE);
+ isc_buffer_setautorealloc(session->pending_write_data,
+ true);
+ }
+ isc_buffer_putmem(session->pending_write_data, data, pending);
+ total = new_total;
+ }
+
+#ifdef ENABLE_HTTP_WRITE_BUFFERING
+ if (session->pending_write_data != NULL) {
+ max_total_write_size =
+ isc_buffer_usedlength(session->pending_write_data);
+ }
+
+ /* Here we are trying to flush the pending writes buffer earlier
+ * to avoid hitting unnecessary limitations on a TLS record size
+ * within some tools (e.g. flamethrower). */
+ if (max_total_write_size >= FLUSH_HTTP_WRITE_BUFFER_AFTER) {
+ /* Case 1: We have equal or more than
+ * FLUSH_HTTP_WRITE_BUFFER_AFTER bytes to send. Let's flush it.
+ */
+ total = max_total_write_size;
+ } else if (session->sending > 0 && total > 0) {
+ /* Case 2: There is one or more write requests in flight and
+ * we have some new data form nghttp2 to send. Let's put the
+ * write callback (if any) into the pending write callbacks
+ * list. Then let's return from the function: as soon as the
+ * "in-flight" write callback get's called or we have reached
+ * FLUSH_HTTP_WRITE_BUFFER_AFTER bytes in the write buffer, we
+ * will flush the buffer. */
+ if (cb != NULL) {
+ isc__nm_uvreq_t *newcb = isc__nm_uvreq_get(
+ httphandle->sock->mgr, httphandle->sock);
+
+ INSIST(VALID_NMHANDLE(httphandle));
+ newcb->cb.send = cb;
+ newcb->cbarg = cbarg;
+ isc_nmhandle_attach(httphandle, &newcb->handle);
+ ISC_LIST_APPEND(session->pending_write_callbacks, newcb,
+ link);
+ }
+ goto nothing_to_send;
+ } else if (session->sending == 0 && total == 0 &&
+ session->pending_write_data != NULL)
+ {
+ /* Case 3: There is no write in flight and we haven't got
+ * anything new from nghttp2, but there is some data pending
+ * in the write buffer. Let's flush the buffer. */
+ isc_region_t region = { 0 };
+ total = isc_buffer_usedlength(session->pending_write_data);
+ INSIST(total > 0);
+ isc_buffer_usedregion(session->pending_write_data, &region);
+ INSIST(total == region.length);
+ } else {
+ /* The other cases are, uninteresting, fall-through ones. */
+ /* In the following cases (4-6) we will just bail out. */
+ /* Case 4: There is nothing new to send, nor anything in the
+ * write buffer. */
+ /* Case 5: There is nothing new to send and there is write
+ * request(s) in flight. */
+ /* Case 6: There is nothing new to send nor there are any
+ * write requests in flight. */
+
+ /* Case 7: There is some new data to send and there are no any
+ * write requests in flight: Let's send the data.*/
+ INSIST((total == 0 && session->pending_write_data == NULL) ||
+ (total == 0 && session->sending > 0) ||
+ (total == 0 && session->sending == 0) ||
+ (total > 0 && session->sending == 0));
+ }
+#else
+ INSIST(ISC_LIST_EMPTY(session->pending_write_callbacks));
+#endif /* ENABLE_HTTP_WRITE_BUFFERING */
+
+ if (total == 0) {
+ /* No data returned */
+ goto nothing_to_send;
+ }
+
+ /* If we have reached the point it means that we need to send some
+ * data and flush the outgoing buffer. The code below does that. */
+ send = isc_mem_get(session->mctx, sizeof(*send));
+
+ *send = (isc_http_send_req_t){ .pending_write_data =
+ session->pending_write_data,
+ .cb = cb,
+ .cbarg = cbarg };
+ session->pending_write_data = NULL;
+ move_pending_send_callbacks(session, send);
+
+ send->transphandle = transphandle;
+ isc__nm_httpsession_attach(session, &send->session);
+
+ if (cb != NULL) {
+ INSIST(VALID_NMHANDLE(httphandle));
+ isc_nmhandle_attach(httphandle, &send->httphandle);
+ }
+
+ session->sending++;
+ isc_buffer_usedregion(send->pending_write_data, &send_data);
+ isc_nm_send(transphandle, &send_data, http_writecb, send);
+ return (true);
+nothing_to_send:
+ isc_nmhandle_detach(&transphandle);
+ return (false);
+}
+
+static void
+http_do_bio(isc_nm_http_session_t *session, isc_nmhandle_t *send_httphandle,
+ isc_nm_cb_t send_cb, void *send_cbarg) {
+ REQUIRE(VALID_HTTP2_SESSION(session));
+
+ if (session->closed) {
+ return;
+ } else if (session->closing) {
+ /*
+ * There might be leftover callbacks waiting to be received
+ */
+ if (session->sending == 0) {
+ finish_http_session(session);
+ }
+ return;
+ } else if (nghttp2_session_want_read(session->ngsession) == 0 &&
+ nghttp2_session_want_write(session->ngsession) == 0 &&
+ session->pending_write_data == NULL)
+ {
+ session->closing = true;
+ return;
+ }
+
+ if (nghttp2_session_want_read(session->ngsession) != 0) {
+ if (!session->reading) {
+ /* We have not yet started reading from this handle */
+ isc_nm_read(session->handle, http_readcb, session);
+ session->reading = true;
+ } else if (session->buf != NULL) {
+ size_t remaining =
+ isc_buffer_remaininglength(session->buf);
+ /* Leftover data in the buffer, use it */
+ size_t readlen = nghttp2_session_mem_recv(
+ session->ngsession,
+ isc_buffer_current(session->buf), remaining);
+
+ if (readlen == remaining) {
+ isc_buffer_free(&session->buf);
+ } else {
+ isc_buffer_forward(session->buf, readlen);
+ }
+
+ http_do_bio(session, send_httphandle, send_cb,
+ send_cbarg);
+ return;
+ } else {
+ /* Resume reading, it's idempotent, wait for more */
+ isc_nm_resumeread(session->handle);
+ }
+ } else {
+ /* We don't want more data, stop reading for now */
+ isc_nm_pauseread(session->handle);
+ }
+
+ if (send_cb != NULL) {
+ INSIST(VALID_NMHANDLE(send_httphandle));
+ (void)http_send_outgoing(session, send_httphandle, send_cb,
+ send_cbarg);
+ } else {
+ INSIST(send_httphandle == NULL);
+ INSIST(send_cb == NULL);
+ INSIST(send_cbarg == NULL);
+ (void)http_send_outgoing(session, NULL, NULL, NULL);
+ }
+
+ return;
+}
+
+static isc_result_t
+get_http_cstream(isc_nmsocket_t *sock, http_cstream_t **streamp) {
+ http_cstream_t *cstream = sock->h2.connect.cstream;
+ isc_result_t result;
+
+ REQUIRE(streamp != NULL && *streamp == NULL);
+
+ sock->h2.connect.cstream = NULL;
+
+ if (cstream == NULL) {
+ result = new_http_cstream(sock, &cstream);
+ if (result != ISC_R_SUCCESS) {
+ INSIST(cstream == NULL);
+ return (result);
+ }
+ }
+
+ *streamp = cstream;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+http_call_connect_cb(isc_nmsocket_t *sock, isc_nm_http_session_t *session,
+ isc_result_t result) {
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmhandle_t *httphandle = isc__nmhandle_get(sock, &sock->peer,
+ &sock->iface);
+
+ REQUIRE(sock->connect_cb != NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ req = isc__nm_uvreq_get(sock->mgr, sock);
+ req->cb.connect = sock->connect_cb;
+ req->cbarg = sock->connect_cbarg;
+ if (session != NULL) {
+ session->client_httphandle = httphandle;
+ req->handle = NULL;
+ isc_nmhandle_attach(httphandle, &req->handle);
+ } else {
+ req->handle = httphandle;
+ }
+
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ } else {
+ void *cbarg = sock->connect_cbarg;
+ isc_nm_cb_t connect_cb = sock->connect_cb;
+
+ isc__nmsocket_clearcb(sock);
+ connect_cb(httphandle, result, cbarg);
+ isc_nmhandle_detach(&httphandle);
+ }
+}
+
+static void
+transport_connect_cb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ isc_nmsocket_t *http_sock = (isc_nmsocket_t *)cbarg;
+ isc_nmsocket_t *transp_sock = NULL;
+ isc_nm_http_session_t *session = NULL;
+ http_cstream_t *cstream = NULL;
+ isc_mem_t *mctx = NULL;
+
+ REQUIRE(VALID_NMSOCK(http_sock));
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ transp_sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(transp_sock));
+
+ mctx = transp_sock->mgr->mctx;
+
+ INSIST(http_sock->h2.connect.uri != NULL);
+
+ http_sock->tid = transp_sock->tid;
+ http_sock->h2.connect.tls_peer_verify_string =
+ isc_nm_verify_tls_peer_result_string(handle);
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ new_session(mctx, http_sock->h2.connect.tlsctx, &session);
+ session->client = true;
+ transp_sock->h2.session = session;
+ http_sock->h2.connect.tlsctx = NULL;
+ /* otherwise we will get some garbage output in DIG */
+ http_sock->iface = handle->sock->iface;
+ http_sock->peer = handle->sock->peer;
+
+ transp_sock->h2.connect.post = http_sock->h2.connect.post;
+ transp_sock->h2.connect.uri = http_sock->h2.connect.uri;
+ http_sock->h2.connect.uri = NULL;
+ isc__nm_httpsession_attach(session, &http_sock->h2.session);
+
+ if (session->tlsctx != NULL) {
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+
+ INSIST(transp_sock->type == isc_nm_tlssocket);
+
+ isc_tls_get_selected_alpn(transp_sock->tlsstream.tls, &alpn,
+ &alpnlen);
+ if (alpn == NULL || alpnlen != NGHTTP2_PROTO_VERSION_ID_LEN ||
+ memcmp(NGHTTP2_PROTO_VERSION_ID, alpn,
+ NGHTTP2_PROTO_VERSION_ID_LEN) != 0)
+ {
+ /*
+ * HTTP/2 negotiation error. Any sensible DoH
+ * client will fail if HTTP/2 cannot be
+ * negotiated via ALPN.
+ */
+ result = ISC_R_HTTP2ALPNERROR;
+ goto error;
+ }
+ }
+
+ isc_nmhandle_attach(handle, &session->handle);
+
+ initialize_nghttp2_client_session(session);
+ if (!send_client_connection_header(session)) {
+ goto error;
+ }
+
+ result = get_http_cstream(http_sock, &cstream);
+ http_sock->h2.connect.cstream = cstream;
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ http_transpost_tcp_nodelay(handle);
+
+ http_call_connect_cb(http_sock, session, result);
+
+ http_do_bio(session, NULL, NULL, NULL);
+ isc__nmsocket_detach(&http_sock);
+ return;
+
+error:
+ http_call_connect_cb(http_sock, session, result);
+
+ if (http_sock->h2.connect.uri != NULL) {
+ isc_mem_free(mctx, http_sock->h2.connect.uri);
+ }
+
+ isc__nmsocket_prep_destroy(http_sock);
+ isc__nmsocket_detach(&http_sock);
+}
+
+void
+isc_nm_httpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ const char *uri, bool post, isc_nm_cb_t cb, void *cbarg,
+ isc_tlsctx_t *tlsctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ unsigned int timeout, size_t extrahandlesize) {
+ isc_sockaddr_t local_interface;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(cb != NULL);
+ REQUIRE(peer != NULL);
+ REQUIRE(uri != NULL);
+ REQUIRE(*uri != '\0');
+
+ if (local == NULL) {
+ isc_sockaddr_anyofpf(&local_interface, peer->type.sa.sa_family);
+ local = &local_interface;
+ }
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_httpsocket, local);
+
+ sock->extrahandlesize = extrahandlesize;
+ sock->connect_timeout = timeout;
+ sock->result = ISC_R_UNSET;
+ sock->connect_cb = cb;
+ sock->connect_cbarg = cbarg;
+ atomic_init(&sock->client, true);
+
+ if (isc__nm_closing(sock)) {
+ isc__nm_uvreq_t *req = isc__nm_uvreq_get(mgr, sock);
+
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->peer = *peer;
+ req->local = *local;
+ req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface);
+
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ }
+
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, ISC_R_SHUTTINGDOWN, true);
+ isc__nmsocket_prep_destroy(sock);
+ isc__nmsocket_detach(&sock);
+ return;
+ }
+
+ sock->h2 = (isc_nmsocket_h2_t){ .connect.uri = isc_mem_strdup(mgr->mctx,
+ uri),
+ .connect.post = post,
+ .connect.tlsctx = tlsctx };
+ ISC_LINK_INIT(&sock->h2, link);
+
+ /*
+ * We need to prevent the interface object data from going out of
+ * scope too early.
+ */
+ if (local == &local_interface) {
+ sock->h2.connect.local_interface = local_interface;
+ sock->iface = sock->h2.connect.local_interface;
+ }
+
+ if (tlsctx != NULL) {
+ isc_nm_tlsconnect(mgr, local, peer, transport_connect_cb, sock,
+ tlsctx, client_sess_cache, timeout, 0);
+ } else {
+ isc_nm_tcpconnect(mgr, local, peer, transport_connect_cb, sock,
+ timeout, 0);
+ }
+}
+
+static isc_result_t
+client_send(isc_nmhandle_t *handle, const isc_region_t *region) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = handle->sock;
+ isc_mem_t *mctx = sock->mgr->mctx;
+ isc_nm_http_session_t *session = sock->h2.session;
+ http_cstream_t *cstream = sock->h2.connect.cstream;
+
+ REQUIRE(VALID_HTTP2_SESSION(handle->sock->h2.session));
+ REQUIRE(session->client);
+ REQUIRE(region != NULL);
+ REQUIRE(region->base != NULL);
+ REQUIRE(region->length <= MAX_DNS_MESSAGE_SIZE);
+
+ if (session->closed) {
+ return (ISC_R_CANCELED);
+ }
+
+ INSIST(cstream != NULL);
+
+ if (cstream->post) {
+ /* POST */
+ isc_buffer_allocate(mctx, &cstream->postdata, region->length);
+ isc_buffer_putmem(cstream->postdata, region->base,
+ region->length);
+ } else {
+ /* GET */
+ size_t path_size = 0;
+ char *base64url_data = NULL;
+ size_t base64url_data_len = 0;
+ isc_buffer_t *buf = NULL;
+ isc_region_t data = *region;
+ isc_region_t base64_region;
+ size_t base64_len = ((4 * data.length / 3) + 3) & ~3;
+
+ isc_buffer_allocate(mctx, &buf, base64_len);
+
+ result = isc_base64_totext(&data, -1, "", buf);
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&buf);
+ goto error;
+ }
+
+ isc_buffer_usedregion(buf, &base64_region);
+ INSIST(base64_region.length == base64_len);
+
+ base64url_data = isc__nm_base64_to_base64url(
+ mctx, (const char *)base64_region.base,
+ base64_region.length, &base64url_data_len);
+ isc_buffer_free(&buf);
+ if (base64url_data == NULL) {
+ goto error;
+ }
+
+ /* len("?dns=") + len(path) + len(base64url) + len("\0") */
+ path_size = cstream->pathlen + base64url_data_len + 5 + 1;
+ cstream->GET_path = isc_mem_allocate(mctx, path_size);
+ cstream->GET_path_len = (size_t)snprintf(
+ cstream->GET_path, path_size, "%.*s?dns=%s",
+ (int)cstream->pathlen, cstream->path, base64url_data);
+
+ INSIST(cstream->GET_path_len == (path_size - 1));
+ isc_mem_free(mctx, base64url_data);
+ }
+
+ cstream->sending = true;
+
+ sock->h2.connect.cstream = NULL;
+ result = client_submit_request(session, cstream);
+ if (result != ISC_R_SUCCESS) {
+ put_http_cstream(session->mctx, cstream);
+ goto error;
+ }
+
+error:
+ return (result);
+}
+
+isc_result_t
+isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_recv_cb_t cb, void *cbarg) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ http_cstream_t *cstream = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&handle->sock->client));
+
+ REQUIRE(cb != NULL);
+
+ sock = handle->sock;
+
+ isc__nm_http_read(handle, cb, cbarg);
+ if (!http_session_active(handle->sock->h2.session)) {
+ /* the callback was called by isc__nm_http_read() */
+ return (ISC_R_CANCELED);
+ }
+ result = client_send(handle, region);
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ return (ISC_R_SUCCESS);
+
+error:
+ cstream = sock->h2.connect.cstream;
+ if (cstream->read_cb != NULL) {
+ cstream->read_cb(handle, result, NULL, cstream->read_cbarg);
+ }
+ return (result);
+}
+
+static int
+server_on_begin_headers_callback(nghttp2_session *ngsession,
+ const nghttp2_frame *frame, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ isc_nmsocket_t *socket = NULL;
+
+ if (frame->hd.type != NGHTTP2_HEADERS ||
+ frame->headers.cat != NGHTTP2_HCAT_REQUEST)
+ {
+ return (0);
+ } else if (frame->hd.length > MAX_ALLOWED_DATA_IN_HEADERS) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+
+ if (session->nsstreams >= session->max_concurrent_streams) {
+ return (NGHTTP2_ERR_CALLBACK_FAILURE);
+ }
+
+ socket = isc_mem_get(session->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(socket, session->serversocket->mgr,
+ isc_nm_httpsocket,
+ (isc_sockaddr_t *)&session->handle->sock->iface);
+ socket->peer = session->handle->sock->peer;
+ socket->h2 = (isc_nmsocket_h2_t){
+ .psock = socket,
+ .stream_id = frame->hd.stream_id,
+ .headers_error_code = ISC_HTTP_ERROR_SUCCESS,
+ .request_type = ISC_HTTP_REQ_UNSUPPORTED,
+ .request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED
+ };
+ isc_buffer_initnull(&socket->h2.rbuf);
+ isc_buffer_initnull(&socket->h2.wbuf);
+ session->nsstreams++;
+ isc__nm_httpsession_attach(session, &socket->h2.session);
+ socket->tid = session->handle->sock->tid;
+ ISC_LINK_INIT(&socket->h2, link);
+ ISC_LIST_APPEND(session->sstreams, &socket->h2, link);
+
+ nghttp2_session_set_stream_user_data(ngsession, frame->hd.stream_id,
+ socket);
+ return (0);
+}
+
+static isc_nm_httphandler_t *
+find_server_request_handler(const char *request_path,
+ isc_nmsocket_t *serversocket, const int tid) {
+ isc_nm_httphandler_t *handler = NULL;
+
+ REQUIRE(VALID_NMSOCK(serversocket));
+
+ if (atomic_load(&serversocket->listening)) {
+ handler = http_endpoints_find(
+ request_path,
+ http_get_listener_endpoints(serversocket, tid));
+ }
+ return (handler);
+}
+
+static isc_http_error_responses_t
+server_handle_path_header(isc_nmsocket_t *socket, const uint8_t *value,
+ const size_t valuelen) {
+ isc_nm_httphandler_t *handler = NULL;
+ const uint8_t *qstr = NULL;
+ size_t vlen = valuelen;
+
+ qstr = memchr(value, '?', valuelen);
+ if (qstr != NULL) {
+ vlen = qstr - value;
+ }
+
+ if (socket->h2.request_path != NULL) {
+ isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
+ }
+ socket->h2.request_path = isc_mem_strndup(
+ socket->mgr->mctx, (const char *)value, vlen + 1);
+
+ if (!isc_nm_http_path_isvalid(socket->h2.request_path)) {
+ isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
+ socket->h2.request_path = NULL;
+ return (ISC_HTTP_ERROR_BAD_REQUEST);
+ }
+
+ handler = find_server_request_handler(socket->h2.request_path,
+ socket->h2.session->serversocket,
+ socket->tid);
+ if (handler != NULL) {
+ socket->h2.cb = handler->cb;
+ socket->h2.cbarg = handler->cbarg;
+ socket->extrahandlesize = handler->extrahandlesize;
+ } else {
+ isc_mem_free(socket->mgr->mctx, socket->h2.request_path);
+ socket->h2.request_path = NULL;
+ return (ISC_HTTP_ERROR_NOT_FOUND);
+ }
+
+ if (qstr != NULL) {
+ const char *dns_value = NULL;
+ size_t dns_value_len = 0;
+
+ if (isc__nm_parse_httpquery((const char *)qstr, &dns_value,
+ &dns_value_len))
+ {
+ const size_t decoded_size = dns_value_len / 4 * 3;
+ if (decoded_size <= MAX_DNS_MESSAGE_SIZE) {
+ if (socket->h2.query_data != NULL) {
+ isc_mem_free(socket->mgr->mctx,
+ socket->h2.query_data);
+ }
+ socket->h2.query_data =
+ isc__nm_base64url_to_base64(
+ socket->mgr->mctx, dns_value,
+ dns_value_len,
+ &socket->h2.query_data_len);
+ } else {
+ socket->h2.query_too_large = true;
+ return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE);
+ }
+ } else {
+ return (ISC_HTTP_ERROR_BAD_REQUEST);
+ }
+ }
+ return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http_error_responses_t
+server_handle_method_header(isc_nmsocket_t *socket, const uint8_t *value,
+ const size_t valuelen) {
+ const char get[] = "GET";
+ const char post[] = "POST";
+
+ if (HEADER_MATCH(get, value, valuelen)) {
+ socket->h2.request_type = ISC_HTTP_REQ_GET;
+ } else if (HEADER_MATCH(post, value, valuelen)) {
+ socket->h2.request_type = ISC_HTTP_REQ_POST;
+ } else {
+ return (ISC_HTTP_ERROR_NOT_IMPLEMENTED);
+ }
+ return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http_error_responses_t
+server_handle_scheme_header(isc_nmsocket_t *socket, const uint8_t *value,
+ const size_t valuelen) {
+ const char http[] = "http";
+ const char http_secure[] = "https";
+
+ if (HEADER_MATCH(http_secure, value, valuelen)) {
+ socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP_SECURE;
+ } else if (HEADER_MATCH(http, value, valuelen)) {
+ socket->h2.request_scheme = ISC_HTTP_SCHEME_HTTP;
+ } else {
+ return (ISC_HTTP_ERROR_BAD_REQUEST);
+ }
+ return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http_error_responses_t
+server_handle_content_length_header(isc_nmsocket_t *socket,
+ const uint8_t *value,
+ const size_t valuelen) {
+ char tmp[32] = { 0 };
+ const size_t tmplen = sizeof(tmp) - 1;
+
+ strncpy(tmp, (const char *)value,
+ valuelen > tmplen ? tmplen : valuelen);
+ socket->h2.content_length = strtoul(tmp, NULL, 10);
+ if (socket->h2.content_length > MAX_DNS_MESSAGE_SIZE) {
+ return (ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE);
+ } else if (socket->h2.content_length == 0) {
+ return (ISC_HTTP_ERROR_BAD_REQUEST);
+ }
+ return (ISC_HTTP_ERROR_SUCCESS);
+}
+
+static isc_http_error_responses_t
+server_handle_content_type_header(isc_nmsocket_t *socket, const uint8_t *value,
+ const size_t valuelen) {
+ const char type_dns_message[] = DNS_MEDIA_TYPE;
+ isc_http_error_responses_t resp = ISC_HTTP_ERROR_SUCCESS;
+
+ UNUSED(socket);
+
+ if (!HEADER_MATCH(type_dns_message, value, valuelen)) {
+ resp = ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE;
+ }
+ return (resp);
+}
+
+static isc_http_error_responses_t
+server_handle_header(isc_nmsocket_t *socket, const uint8_t *name,
+ size_t namelen, const uint8_t *value,
+ const size_t valuelen) {
+ isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+ bool was_error;
+ const char path[] = ":path";
+ const char method[] = ":method";
+ const char scheme[] = ":scheme";
+ const char content_length[] = "Content-Length";
+ const char content_type[] = "Content-Type";
+
+ was_error = socket->h2.headers_error_code != ISC_HTTP_ERROR_SUCCESS;
+ /*
+ * process Content-Length even when there was an error,
+ * to drop the connection earlier if required.
+ */
+ if (HEADER_MATCH(content_length, name, namelen)) {
+ code = server_handle_content_length_header(socket, value,
+ valuelen);
+ } else if (!was_error && HEADER_MATCH(path, name, namelen)) {
+ code = server_handle_path_header(socket, value, valuelen);
+ } else if (!was_error && HEADER_MATCH(method, name, namelen)) {
+ code = server_handle_method_header(socket, value, valuelen);
+ } else if (!was_error && HEADER_MATCH(scheme, name, namelen)) {
+ code = server_handle_scheme_header(socket, value, valuelen);
+ } else if (!was_error && HEADER_MATCH(content_type, name, namelen)) {
+ code = server_handle_content_type_header(socket, value,
+ valuelen);
+ }
+
+ return (code);
+}
+
+static int
+server_on_header_callback(nghttp2_session *session, const nghttp2_frame *frame,
+ const uint8_t *name, size_t namelen,
+ const uint8_t *value, size_t valuelen, uint8_t flags,
+ void *user_data) {
+ isc_nmsocket_t *socket = NULL;
+ isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+
+ UNUSED(flags);
+ UNUSED(user_data);
+
+ socket = nghttp2_session_get_stream_user_data(session,
+ frame->hd.stream_id);
+ if (socket == NULL) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+
+ socket->h2.headers_data_processed += (namelen + valuelen);
+
+ switch (frame->hd.type) {
+ case NGHTTP2_HEADERS:
+ if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) {
+ break;
+ }
+ code = server_handle_header(socket, name, namelen, value,
+ valuelen);
+ break;
+ }
+
+ INSIST(socket != NULL);
+
+ if (socket->h2.headers_data_processed > MAX_ALLOWED_DATA_IN_HEADERS) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ } else if (socket->h2.content_length > MAX_ALLOWED_DATA_IN_POST) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+
+ if (code == ISC_HTTP_ERROR_SUCCESS) {
+ return (0);
+ } else {
+ socket->h2.headers_error_code = code;
+ }
+
+ return (0);
+}
+
+static ssize_t
+server_read_callback(nghttp2_session *ngsession, int32_t stream_id,
+ uint8_t *buf, size_t length, uint32_t *data_flags,
+ nghttp2_data_source *source, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ isc_nmsocket_t *socket = (isc_nmsocket_t *)source->ptr;
+ size_t buflen;
+
+ REQUIRE(socket->h2.stream_id == stream_id);
+
+ UNUSED(ngsession);
+ UNUSED(session);
+
+ buflen = isc_buffer_remaininglength(&socket->h2.wbuf);
+ if (buflen > length) {
+ buflen = length;
+ }
+
+ if (buflen > 0) {
+ (void)memmove(buf, isc_buffer_current(&socket->h2.wbuf),
+ buflen);
+ isc_buffer_forward(&socket->h2.wbuf, buflen);
+ }
+
+ if (isc_buffer_remaininglength(&socket->h2.wbuf) == 0) {
+ *data_flags |= NGHTTP2_DATA_FLAG_EOF;
+ }
+
+ return (buflen);
+}
+
+static isc_result_t
+server_send_response(nghttp2_session *ngsession, int32_t stream_id,
+ const nghttp2_nv *nva, size_t nvlen,
+ isc_nmsocket_t *socket) {
+ nghttp2_data_provider data_prd;
+ int rv;
+
+ if (socket->h2.response_submitted) {
+ /* NGHTTP2 will gladly accept new response (write request)
+ * from us even though we cannot send more than one over the
+ * same HTTP/2 stream. Thus, we need to handle this case
+ * manually. We will return failure code so that it will be
+ * passed to the write callback. */
+ return (ISC_R_FAILURE);
+ }
+
+ data_prd.source.ptr = socket;
+ data_prd.read_callback = server_read_callback;
+
+ rv = nghttp2_submit_response(ngsession, stream_id, nva, nvlen,
+ &data_prd);
+ if (rv != 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ socket->h2.response_submitted = true;
+ return (ISC_R_SUCCESS);
+}
+
+#define MAKE_ERROR_REPLY(tag, code, desc) \
+ { \
+ tag, MAKE_NV2(":status", #code), desc \
+ }
+
+/*
+ * Here we use roughly the same error codes that Unbound uses.
+ * (https://blog.nlnetlabs.nl/dns-over-https-in-unbound/)
+ */
+
+static struct http_error_responses {
+ const isc_http_error_responses_t type;
+ const nghttp2_nv header;
+ const char *desc;
+} error_responses[] = {
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_BAD_REQUEST, 400, "Bad Request"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_FOUND, 404, "Not Found"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE, 413,
+ "Payload Too Large"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_URI_TOO_LONG, 414, "URI Too Long"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_UNSUPPORTED_MEDIA_TYPE, 415,
+ "Unsupported Media Type"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_GENERIC, 500, "Internal Server Error"),
+ MAKE_ERROR_REPLY(ISC_HTTP_ERROR_NOT_IMPLEMENTED, 501, "Not Implemented")
+};
+
+static void
+log_server_error_response(const isc_nmsocket_t *socket,
+ const struct http_error_responses *response) {
+ const int log_level = ISC_LOG_DEBUG(1);
+ isc_sockaddr_t client_addr;
+ isc_sockaddr_t local_addr;
+ char client_sabuf[ISC_SOCKADDR_FORMATSIZE];
+ char local_sabuf[ISC_SOCKADDR_FORMATSIZE];
+
+ if (!isc_log_wouldlog(isc_lctx, log_level)) {
+ return;
+ }
+
+ client_addr = isc_nmhandle_peeraddr(socket->h2.session->handle);
+ local_addr = isc_nmhandle_localaddr(socket->h2.session->handle);
+ isc_sockaddr_format(&client_addr, client_sabuf, sizeof(client_sabuf));
+ isc_sockaddr_format(&local_addr, local_sabuf, sizeof(local_sabuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ log_level, "HTTP/2 request from %s (on %s) failed: %s %s",
+ client_sabuf, local_sabuf, response->header.value,
+ response->desc);
+}
+
+static isc_result_t
+server_send_error_response(const isc_http_error_responses_t error,
+ nghttp2_session *ngsession, isc_nmsocket_t *socket) {
+ void *base;
+
+ REQUIRE(error != ISC_HTTP_ERROR_SUCCESS);
+
+ base = isc_buffer_base(&socket->h2.rbuf);
+ if (base != NULL) {
+ isc_mem_free(socket->h2.session->mctx, base);
+ isc_buffer_initnull(&socket->h2.rbuf);
+ }
+
+ /* We do not want the error response to be cached anywhere. */
+ socket->h2.min_ttl = 0;
+
+ for (size_t i = 0;
+ i < sizeof(error_responses) / sizeof(error_responses[0]); i++)
+ {
+ if (error_responses[i].type == error) {
+ log_server_error_response(socket, &error_responses[i]);
+ return (server_send_response(
+ ngsession, socket->h2.stream_id,
+ &error_responses[i].header, 1, socket));
+ }
+ }
+
+ return (server_send_error_response(ISC_HTTP_ERROR_GENERIC, ngsession,
+ socket));
+}
+
+static void
+server_call_cb(isc_nmsocket_t *socket, isc_nm_http_session_t *session,
+ const isc_result_t result, isc_region_t *data) {
+ isc_sockaddr_t addr;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(socket));
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(socket->h2.cb != NULL);
+
+ addr = isc_nmhandle_peeraddr(session->handle);
+ handle = isc__nmhandle_get(socket, &addr, NULL);
+ socket->h2.cb(handle, result, data, socket->h2.cbarg);
+ isc_nmhandle_detach(&handle);
+}
+
+void
+isc__nm_http_bad_request(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ sock = handle->sock;
+ REQUIRE(sock->type == isc_nm_httpsocket);
+ REQUIRE(!atomic_load(&sock->client));
+ REQUIRE(VALID_HTTP2_SESSION(sock->h2.session));
+
+ (void)server_send_error_response(ISC_HTTP_ERROR_BAD_REQUEST,
+ sock->h2.session->ngsession, sock);
+}
+
+static int
+server_on_request_recv(nghttp2_session *ngsession,
+ isc_nm_http_session_t *session, isc_nmsocket_t *socket) {
+ isc_result_t result;
+ isc_http_error_responses_t code = ISC_HTTP_ERROR_SUCCESS;
+ isc_region_t data;
+ uint8_t tmp_buf[MAX_DNS_MESSAGE_SIZE];
+
+ code = socket->h2.headers_error_code;
+ if (code != ISC_HTTP_ERROR_SUCCESS) {
+ goto error;
+ }
+
+ if (socket->h2.request_path == NULL || socket->h2.cb == NULL) {
+ code = ISC_HTTP_ERROR_NOT_FOUND;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+ socket->h2.content_length == 0)
+ {
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+ isc_buffer_usedlength(&socket->h2.rbuf) >
+ socket->h2.content_length)
+ {
+ code = ISC_HTTP_ERROR_PAYLOAD_TOO_LARGE;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+ isc_buffer_usedlength(&socket->h2.rbuf) !=
+ socket->h2.content_length)
+ {
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_POST &&
+ socket->h2.query_data != NULL)
+ {
+ /* The spec does not mention which value the query string for
+ * POST should have. For GET we use its value to decode a DNS
+ * message from it, for POST the message is transferred in the
+ * body of the request. Taking it into account, it is much safer
+ * to treat POST
+ * requests with query strings as malformed ones. */
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_GET &&
+ socket->h2.content_length > 0)
+ {
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_GET &&
+ socket->h2.query_data == NULL)
+ {
+ /* A GET request without any query data - there is nothing to
+ * decode. */
+ INSIST(socket->h2.query_data_len == 0);
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ }
+
+ if (code != ISC_HTTP_ERROR_SUCCESS) {
+ goto error;
+ }
+
+ if (socket->h2.request_type == ISC_HTTP_REQ_GET) {
+ isc_buffer_t decoded_buf;
+ isc_buffer_init(&decoded_buf, tmp_buf, sizeof(tmp_buf));
+ if (isc_base64_decodestring(socket->h2.query_data,
+ &decoded_buf) != ISC_R_SUCCESS)
+ {
+ code = ISC_HTTP_ERROR_BAD_REQUEST;
+ goto error;
+ }
+ isc_buffer_usedregion(&decoded_buf, &data);
+ } else if (socket->h2.request_type == ISC_HTTP_REQ_POST) {
+ INSIST(socket->h2.content_length > 0);
+ isc_buffer_usedregion(&socket->h2.rbuf, &data);
+ } else {
+ UNREACHABLE();
+ }
+
+ server_call_cb(socket, session, ISC_R_SUCCESS, &data);
+
+ return (0);
+
+error:
+ result = server_send_error_response(code, ngsession, socket);
+ if (result != ISC_R_SUCCESS) {
+ return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
+ }
+ return (0);
+}
+
+void
+isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_httpsend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+ isc_nmhandle_attach(handle, &uvreq->handle);
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ ievent = isc__nm_get_netievent_httpsend(sock->mgr, sock, uvreq);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+static void
+failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_result_t eresult) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+
+ if (req->cb.send != NULL) {
+ isc__nm_sendcb(sock, req, eresult, true);
+ } else {
+ isc__nm_uvreq_put(&req, sock);
+ }
+}
+
+static void
+client_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
+ isc__nm_uvreq_t *req) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nm_cb_t cb = req->cb.send;
+ void *cbarg = req->cbarg;
+
+ result = client_send(
+ handle,
+ &(isc_region_t){ (uint8_t *)req->uvbuf.base, req->uvbuf.len });
+ if (result != ISC_R_SUCCESS) {
+ failed_send_cb(sock, req, result);
+ return;
+ }
+
+ http_do_bio(sock->h2.session, handle, cb, cbarg);
+ isc__nm_uvreq_put(&req, sock);
+}
+
+static void
+server_httpsend(isc_nmhandle_t *handle, isc_nmsocket_t *sock,
+ isc__nm_uvreq_t *req) {
+ size_t content_len_buf_len, cache_control_buf_len;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nm_cb_t cb = req->cb.send;
+ void *cbarg = req->cbarg;
+ if (isc__nmsocket_closing(sock) ||
+ !http_session_active(handle->httpsession))
+ {
+ failed_send_cb(sock, req, ISC_R_CANCELED);
+ return;
+ }
+
+ INSIST(handle->httpsession->handle->sock->tid == isc_nm_tid());
+ INSIST(VALID_NMHANDLE(handle->httpsession->handle));
+ INSIST(VALID_NMSOCK(handle->httpsession->handle->sock));
+
+ isc_buffer_init(&sock->h2.wbuf, req->uvbuf.base, req->uvbuf.len);
+ isc_buffer_add(&sock->h2.wbuf, req->uvbuf.len);
+
+ content_len_buf_len = snprintf(sock->h2.clenbuf,
+ sizeof(sock->h2.clenbuf), "%lu",
+ (unsigned long)req->uvbuf.len);
+ if (sock->h2.min_ttl == 0) {
+ cache_control_buf_len =
+ snprintf(sock->h2.cache_control_buf,
+ sizeof(sock->h2.cache_control_buf), "%s",
+ DEFAULT_CACHE_CONTROL);
+ } else {
+ cache_control_buf_len =
+ snprintf(sock->h2.cache_control_buf,
+ sizeof(sock->h2.cache_control_buf),
+ "max-age=%" PRIu32, sock->h2.min_ttl);
+ }
+ const nghttp2_nv hdrs[] = { MAKE_NV2(":status", "200"),
+ MAKE_NV2("Content-Type", DNS_MEDIA_TYPE),
+ MAKE_NV("Content-Length", sock->h2.clenbuf,
+ content_len_buf_len),
+ MAKE_NV("Cache-Control",
+ sock->h2.cache_control_buf,
+ cache_control_buf_len) };
+
+ result = server_send_response(handle->httpsession->ngsession,
+ sock->h2.stream_id, hdrs,
+ sizeof(hdrs) / sizeof(nghttp2_nv), sock);
+
+ if (result == ISC_R_SUCCESS) {
+ http_do_bio(handle->httpsession, handle, cb, cbarg);
+ } else {
+ cb(handle, result, cbarg);
+ }
+ isc__nm_uvreq_put(&req, sock);
+}
+
+void
+isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_httpsend_t *ievent = (isc__netievent_httpsend_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_nmhandle_t *handle = NULL;
+ isc_nm_http_session_t *session = NULL;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_HTTP2_SESSION(sock->h2.session));
+
+ ievent->req = NULL;
+ handle = req->handle;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ session = sock->h2.session;
+ if (session != NULL && session->client) {
+ client_httpsend(handle, sock, req);
+ } else {
+ server_httpsend(handle, sock, req);
+ }
+}
+
+void
+isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ isc_result_t result;
+ http_cstream_t *cstream = NULL;
+ isc_nm_http_session_t *session = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ session = handle->sock->h2.session;
+ if (!http_session_active(session)) {
+ cb(handle, ISC_R_CANCELED, NULL, cbarg);
+ return;
+ }
+
+ result = get_http_cstream(handle->sock, &cstream);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ handle->sock->h2.connect.cstream = cstream;
+ cstream->read_cb = cb;
+ cstream->read_cbarg = cbarg;
+ cstream->reading = true;
+
+ if (cstream->sending) {
+ result = client_submit_request(session, cstream);
+ if (result != ISC_R_SUCCESS) {
+ put_http_cstream(session->mctx, cstream);
+ return;
+ }
+
+ http_do_bio(session, NULL, NULL, NULL);
+ }
+}
+
+static int
+server_on_frame_recv_callback(nghttp2_session *ngsession,
+ const nghttp2_frame *frame, void *user_data) {
+ isc_nm_http_session_t *session = (isc_nm_http_session_t *)user_data;
+ isc_nmsocket_t *socket = NULL;
+
+ switch (frame->hd.type) {
+ case NGHTTP2_DATA:
+ case NGHTTP2_HEADERS:
+ /* Check that the client request has finished */
+ if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+ socket = nghttp2_session_get_stream_user_data(
+ ngsession, frame->hd.stream_id);
+
+ /*
+ * For DATA and HEADERS frame, this callback may be
+ * called after on_stream_close_callback. Check that
+ * the stream is still alive.
+ */
+ if (socket == NULL) {
+ return (0);
+ }
+
+ return (server_on_request_recv(ngsession, session,
+ socket));
+ }
+ break;
+ default:
+ break;
+ }
+ return (0);
+}
+
+static void
+initialize_nghttp2_server_session(isc_nm_http_session_t *session) {
+ nghttp2_session_callbacks *callbacks = NULL;
+ nghttp2_mem mem;
+
+ init_nghttp2_mem(session->mctx, &mem);
+
+ RUNTIME_CHECK(nghttp2_session_callbacks_new(&callbacks) == 0);
+
+ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+ callbacks, on_data_chunk_recv_callback);
+
+ nghttp2_session_callbacks_set_on_stream_close_callback(
+ callbacks, on_stream_close_callback);
+
+ nghttp2_session_callbacks_set_on_header_callback(
+ callbacks, server_on_header_callback);
+
+ nghttp2_session_callbacks_set_on_begin_headers_callback(
+ callbacks, server_on_begin_headers_callback);
+
+ nghttp2_session_callbacks_set_on_frame_recv_callback(
+ callbacks, server_on_frame_recv_callback);
+
+ RUNTIME_CHECK(nghttp2_session_server_new3(&session->ngsession,
+ callbacks, session, NULL,
+ &mem) == 0);
+
+ nghttp2_session_callbacks_del(callbacks);
+}
+
+static int
+server_send_connection_header(isc_nm_http_session_t *session) {
+ nghttp2_settings_entry iv[1] = {
+ { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
+ session->max_concurrent_streams }
+ };
+ int rv;
+
+ rv = nghttp2_submit_settings(session->ngsession, NGHTTP2_FLAG_NONE, iv,
+ 1);
+ if (rv != 0) {
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * It is advisable to disable Nagle's algorithm for HTTP/2
+ * connections because multiple HTTP/2 streams could be multiplexed
+ * over one transport connection. Thus, delays when delivering small
+ * packets could bring down performance for the whole session.
+ * HTTP/2 is meant to be used this way.
+ */
+static void
+http_transpost_tcp_nodelay(isc_nmhandle_t *transphandle) {
+ isc_nmsocket_t *tcpsock = NULL;
+ uv_os_fd_t tcp_fd = (uv_os_fd_t)-1;
+
+ if (transphandle->sock->type == isc_nm_tlssocket) {
+ tcpsock = transphandle->sock->outerhandle->sock;
+ } else {
+ tcpsock = transphandle->sock;
+ }
+
+ (void)uv_fileno((uv_handle_t *)&tcpsock->uv_handle.tcp, &tcp_fd);
+ RUNTIME_CHECK(tcp_fd != (uv_os_fd_t)-1);
+ (void)isc__nm_socket_tcp_nodelay((uv_os_sock_t)tcp_fd);
+}
+
+static isc_result_t
+httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ isc_nmsocket_t *httplistensock = (isc_nmsocket_t *)cbarg;
+ isc_nm_http_session_t *session = NULL;
+ isc_nmsocket_t *listener = NULL, *httpserver = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ if (handle->sock->type == isc_nm_tlssocket) {
+ REQUIRE(VALID_NMSOCK(handle->sock->listener));
+ listener = handle->sock->listener;
+ httpserver = listener->h2.httpserver;
+ } else {
+ REQUIRE(VALID_NMSOCK(handle->sock->server));
+ listener = handle->sock->server;
+ REQUIRE(VALID_NMSOCK(listener->parent));
+ httpserver = listener->parent->h2.httpserver;
+ }
+
+ /*
+ * NOTE: HTTP listener socket might be destroyed by the time this
+ * function gets invoked, so we need to do extra sanity checks to
+ * detect this case.
+ */
+ if (isc__nmsocket_closing(handle->sock) || httpserver == NULL) {
+ return (ISC_R_CANCELED);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ /* XXXWPK do nothing? */
+ return (result);
+ }
+
+ REQUIRE(VALID_NMSOCK(httplistensock));
+ INSIST(httplistensock == httpserver);
+
+ if (isc__nmsocket_closing(httplistensock) ||
+ !atomic_load(&httplistensock->listening))
+ {
+ return (ISC_R_CANCELED);
+ }
+
+ http_transpost_tcp_nodelay(handle);
+
+ new_session(httplistensock->mgr->mctx, NULL, &session);
+ session->max_concurrent_streams =
+ atomic_load(&httplistensock->h2.max_concurrent_streams);
+ initialize_nghttp2_server_session(session);
+ handle->sock->h2.session = session;
+
+ isc_nmhandle_attach(handle, &session->handle);
+ isc__nmsocket_attach(httplistensock, &session->serversocket);
+ server_send_connection_header(session);
+
+ /* TODO H2 */
+ http_do_bio(session, NULL, NULL, NULL);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog,
+ isc_quota_t *quota, isc_tlsctx_t *ctx,
+ isc_nm_http_endpoints_t *eps, uint32_t max_concurrent_streams,
+ isc_nmsocket_t **sockp) {
+ isc_nmsocket_t *sock = NULL;
+ isc_result_t result;
+
+ REQUIRE(!ISC_LIST_EMPTY(eps->handlers));
+ REQUIRE(!ISC_LIST_EMPTY(eps->handler_cbargs));
+ REQUIRE(atomic_load(&eps->in_use) == false);
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface);
+ atomic_init(&sock->h2.max_concurrent_streams,
+ NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS);
+
+ isc_nmsocket_set_max_streams(sock, max_concurrent_streams);
+
+ atomic_store(&eps->in_use, true);
+ http_init_listener_endpoints(sock, eps);
+
+ if (ctx != NULL) {
+ result = isc_nm_listentls(mgr, iface, httplisten_acceptcb, sock,
+ sizeof(isc_nm_http_session_t),
+ backlog, quota, ctx, &sock->outer);
+ } else {
+ result = isc_nm_listentcp(mgr, iface, httplisten_acceptcb, sock,
+ sizeof(isc_nm_http_session_t),
+ backlog, quota, &sock->outer);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+ return (result);
+ }
+
+ isc__nmsocket_attach(sock, &sock->outer->h2.httpserver);
+
+ sock->nchildren = sock->outer->nchildren;
+ sock->result = ISC_R_UNSET;
+ sock->tid = 0;
+ sock->fd = (uv_os_sock_t)-1;
+
+ isc__nmsocket_barrier_init(sock);
+ atomic_init(&sock->rchildren, sock->nchildren);
+
+ atomic_store(&sock->listening, true);
+ *sockp = sock;
+ return (ISC_R_SUCCESS);
+}
+
+isc_nm_http_endpoints_t *
+isc_nm_http_endpoints_new(isc_mem_t *mctx) {
+ isc_nm_http_endpoints_t *restrict eps;
+ REQUIRE(mctx != NULL);
+
+ eps = isc_mem_get(mctx, sizeof(*eps));
+ *eps = (isc_nm_http_endpoints_t){ .mctx = NULL };
+
+ isc_mem_attach(mctx, &eps->mctx);
+ ISC_LIST_INIT(eps->handler_cbargs);
+ ISC_LIST_INIT(eps->handlers);
+ isc_refcount_init(&eps->references, 1);
+ atomic_init(&eps->in_use, false);
+ eps->magic = HTTP_ENDPOINTS_MAGIC;
+
+ return eps;
+}
+
+void
+isc_nm_http_endpoints_detach(isc_nm_http_endpoints_t **restrict epsp) {
+ isc_nm_http_endpoints_t *restrict eps;
+ isc_mem_t *mctx;
+ isc_nm_httphandler_t *handler = NULL;
+ isc_nm_httpcbarg_t *httpcbarg = NULL;
+
+ REQUIRE(epsp != NULL);
+ eps = *epsp;
+ REQUIRE(VALID_HTTP_ENDPOINTS(eps));
+
+ if (isc_refcount_decrement(&eps->references) > 1) {
+ *epsp = NULL;
+ return;
+ }
+
+ mctx = eps->mctx;
+
+ /* Delete all handlers */
+ handler = ISC_LIST_HEAD(eps->handlers);
+ while (handler != NULL) {
+ isc_nm_httphandler_t *next = NULL;
+
+ next = ISC_LIST_NEXT(handler, link);
+ ISC_LIST_DEQUEUE(eps->handlers, handler, link);
+ isc_mem_free(mctx, handler->path);
+ isc_mem_put(mctx, handler, sizeof(*handler));
+ handler = next;
+ }
+
+ httpcbarg = ISC_LIST_HEAD(eps->handler_cbargs);
+ while (httpcbarg != NULL) {
+ isc_nm_httpcbarg_t *next = NULL;
+
+ next = ISC_LIST_NEXT(httpcbarg, link);
+ ISC_LIST_DEQUEUE(eps->handler_cbargs, httpcbarg, link);
+ isc_mem_put(mctx, httpcbarg, sizeof(isc_nm_httpcbarg_t));
+ httpcbarg = next;
+ }
+
+ eps->magic = 0;
+
+ isc_mem_putanddetach(&mctx, eps, sizeof(*eps));
+ *epsp = NULL;
+}
+
+void
+isc_nm_http_endpoints_attach(isc_nm_http_endpoints_t *source,
+ isc_nm_http_endpoints_t **targetp) {
+ REQUIRE(VALID_HTTP_ENDPOINTS(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static isc_nm_httphandler_t *
+http_endpoints_find(const char *request_path,
+ const isc_nm_http_endpoints_t *restrict eps) {
+ isc_nm_httphandler_t *handler = NULL;
+
+ REQUIRE(VALID_HTTP_ENDPOINTS(eps));
+
+ if (request_path == NULL || *request_path == '\0') {
+ return (NULL);
+ }
+
+ for (handler = ISC_LIST_HEAD(eps->handlers); handler != NULL;
+ handler = ISC_LIST_NEXT(handler, link))
+ {
+ if (!strcmp(request_path, handler->path)) {
+ break;
+ }
+ }
+
+ return (handler);
+}
+
+/*
+ * In DoH we just need to intercept the request - the response can be sent
+ * to the client code via the nmhandle directly as it's always just the
+ * http content.
+ */
+static void
+http_callback(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *data,
+ void *arg) {
+ isc_nm_httpcbarg_t *httpcbarg = arg;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ if (result != ISC_R_SUCCESS) {
+ /* Shut down the client, then ourselves */
+ httpcbarg->cb(handle, result, NULL, httpcbarg->cbarg);
+ /* XXXWPK FREE */
+ return;
+ }
+ httpcbarg->cb(handle, result, data, httpcbarg->cbarg);
+}
+
+isc_result_t
+isc_nm_http_endpoints_add(isc_nm_http_endpoints_t *restrict eps,
+ const char *uri, const isc_nm_recv_cb_t cb,
+ void *cbarg, const size_t extrahandlesize) {
+ isc_mem_t *mctx;
+ isc_nm_httphandler_t *restrict handler = NULL;
+ isc_nm_httpcbarg_t *restrict httpcbarg = NULL;
+ bool newhandler = false;
+
+ REQUIRE(VALID_HTTP_ENDPOINTS(eps));
+ REQUIRE(isc_nm_http_path_isvalid(uri));
+ REQUIRE(atomic_load(&eps->in_use) == false);
+
+ mctx = eps->mctx;
+
+ httpcbarg = isc_mem_get(mctx, sizeof(isc_nm_httpcbarg_t));
+ *httpcbarg = (isc_nm_httpcbarg_t){ .cb = cb, .cbarg = cbarg };
+ ISC_LINK_INIT(httpcbarg, link);
+
+ if (http_endpoints_find(uri, eps) == NULL) {
+ handler = isc_mem_get(mctx, sizeof(*handler));
+ *handler = (isc_nm_httphandler_t){
+ .cb = http_callback,
+ .cbarg = httpcbarg,
+ .extrahandlesize = extrahandlesize,
+ .path = isc_mem_strdup(mctx, uri)
+ };
+ ISC_LINK_INIT(handler, link);
+
+ newhandler = true;
+ }
+
+ if (newhandler) {
+ ISC_LIST_APPEND(eps->handlers, handler, link);
+ }
+ ISC_LIST_APPEND(eps->handler_cbargs, httpcbarg, link);
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_http_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_httplistener);
+
+ isc__nmsocket_stop(sock);
+}
+
+static void
+http_close_direct(isc_nmsocket_t *sock) {
+ isc_nm_http_session_t *session = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ atomic_store(&sock->closed, true);
+ atomic_store(&sock->active, false);
+ session = sock->h2.session;
+
+ if (session != NULL && session->sending == 0 && !session->reading) {
+ /*
+ * The socket is going to be closed too early without been
+ * used even once (might happen in a case of low level
+ * error).
+ */
+ finish_http_session(session);
+ } else if (session != NULL && session->handle) {
+ http_do_bio(session, NULL, NULL, NULL);
+ }
+}
+
+void
+isc__nm_http_close(isc_nmsocket_t *sock) {
+ bool destroy = false;
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_httpsocket);
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ if (sock->h2.session != NULL && sock->h2.session->closed &&
+ sock->tid == isc_nm_tid())
+ {
+ isc__nm_httpsession_detach(&sock->h2.session);
+ destroy = true;
+ } else if (sock->h2.session == NULL && sock->tid == isc_nm_tid()) {
+ destroy = true;
+ }
+
+ if (destroy) {
+ http_close_direct(sock);
+ isc__nmsocket_prep_destroy(sock);
+ return;
+ }
+
+ isc__netievent_httpclose_t *ievent =
+ isc__nm_get_netievent_httpclose(sock->mgr, sock);
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_httpclose_t *ievent = (isc__netievent_httpclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ http_close_direct(sock);
+}
+
+static void
+failed_httpstream_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+ isc_nm_http_session_t *session) {
+ isc_region_t data;
+ REQUIRE(VALID_NMSOCK(sock));
+ INSIST(sock->type == isc_nm_httpsocket);
+
+ if (sock->h2.request_path == NULL) {
+ return;
+ }
+
+ INSIST(sock->h2.cbarg != NULL);
+
+ (void)nghttp2_submit_rst_stream(
+ session->ngsession, NGHTTP2_FLAG_END_STREAM, sock->h2.stream_id,
+ NGHTTP2_REFUSED_STREAM);
+ isc_buffer_usedregion(&sock->h2.rbuf, &data);
+ server_call_cb(sock, session, result, &data);
+}
+
+static void
+client_call_failed_read_cb(isc_result_t result,
+ isc_nm_http_session_t *session) {
+ http_cstream_t *cstream = NULL;
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ cstream = ISC_LIST_HEAD(session->cstreams);
+ while (cstream != NULL) {
+ http_cstream_t *next = ISC_LIST_NEXT(cstream, link);
+
+ /*
+ * read_cb could be NULL if cstream was allocated and added
+ * to the tracking list, but was not properly initialized due
+ * to a low-level error. It is safe to get rid of the object
+ * in such a case.
+ */
+ if (cstream->read_cb != NULL) {
+ isc_region_t read_data;
+ isc_buffer_usedregion(cstream->rbuf, &read_data);
+ cstream->read_cb(session->client_httphandle, result,
+ &read_data, cstream->read_cbarg);
+ }
+
+ if (result != ISC_R_TIMEDOUT || cstream->read_cb == NULL ||
+ !isc__nmsocket_timer_running(session->handle->sock))
+ {
+ ISC_LIST_DEQUEUE(session->cstreams, cstream, link);
+ put_http_cstream(session->mctx, cstream);
+ }
+
+ cstream = next;
+ }
+}
+
+static void
+server_call_failed_read_cb(isc_result_t result,
+ isc_nm_http_session_t *session) {
+ isc_nmsocket_h2_t *h2data = NULL; /* stream socket */
+
+ REQUIRE(VALID_HTTP2_SESSION(session));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ for (h2data = ISC_LIST_HEAD(session->sstreams); h2data != NULL;
+ h2data = ISC_LIST_NEXT(h2data, link))
+ {
+ failed_httpstream_read_cb(h2data->psock, result, session);
+ }
+
+ h2data = ISC_LIST_HEAD(session->sstreams);
+ while (h2data != NULL) {
+ isc_nmsocket_h2_t *next = ISC_LIST_NEXT(h2data, link);
+ ISC_LIST_DEQUEUE(session->sstreams, h2data, link);
+ /* Cleanup socket in place */
+ atomic_store(&h2data->psock->active, false);
+ atomic_store(&h2data->psock->closed, true);
+ isc__nmsocket_detach(&h2data->psock);
+
+ h2data = next;
+ }
+}
+
+static void
+failed_read_cb(isc_result_t result, isc_nm_http_session_t *session) {
+ if (session->client) {
+ client_call_failed_read_cb(result, session);
+ /*
+ * If result was ISC_R_TIMEDOUT and the timer was reset,
+ * then we still have active streams and should not close
+ * the session.
+ */
+ if (ISC_LIST_EMPTY(session->cstreams)) {
+ finish_http_session(session);
+ }
+ } else {
+ server_call_failed_read_cb(result, session);
+ /*
+ * All streams are now destroyed; close the session.
+ */
+ finish_http_session(session);
+ }
+}
+
+void
+isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) {
+ isc_nm_http_session_t *session;
+ isc_nmsocket_t *sock;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+ session = sock->h2.session;
+
+ INSIST(VALID_HTTP2_SESSION(session));
+ INSIST(!session->client);
+
+ sock->h2.min_ttl = ttl;
+}
+
+bool
+isc__nm_http_has_encryption(const isc_nmhandle_t *handle) {
+ isc_nm_http_session_t *session;
+ isc_nmsocket_t *sock;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+ session = sock->h2.session;
+
+ INSIST(VALID_HTTP2_SESSION(session));
+
+ return (isc_nm_socket_type(session->handle) == isc_nm_tlssocket);
+}
+
+const char *
+isc__nm_http_verify_tls_peer_result_string(const isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc_nm_http_session_t *session;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_httpsocket);
+
+ sock = handle->sock;
+ session = sock->h2.session;
+
+ /*
+ * In the case of a low-level error the session->handle is not
+ * attached nor session object is created.
+ */
+ if (session == NULL && sock->h2.connect.tls_peer_verify_string != NULL)
+ {
+ return (sock->h2.connect.tls_peer_verify_string);
+ }
+
+ if (session == NULL) {
+ return (NULL);
+ }
+
+ INSIST(VALID_HTTP2_SESSION(session));
+
+ return (isc_nm_verify_tls_peer_result_string(session->handle));
+}
+
+void
+isc__nm_http_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) {
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(listener->type == isc_nm_httplistener);
+
+ isc_nmsocket_set_tlsctx(listener->outer, tlsctx);
+}
+
+void
+isc__nm_http_set_max_streams(isc_nmsocket_t *listener,
+ const uint32_t max_concurrent_streams) {
+ uint32_t max_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(listener->type == isc_nm_httplistener);
+
+ if (max_concurrent_streams > 0 &&
+ max_concurrent_streams < NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS)
+ {
+ max_streams = max_concurrent_streams;
+ }
+
+ atomic_store(&listener->h2.max_concurrent_streams, max_streams);
+}
+
+void
+isc_nm_http_set_endpoints(isc_nmsocket_t *listener,
+ isc_nm_http_endpoints_t *eps) {
+ size_t nworkers;
+
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(listener->type == isc_nm_httplistener);
+ REQUIRE(VALID_HTTP_ENDPOINTS(eps));
+
+ atomic_store(&eps->in_use, true);
+
+ nworkers = (size_t)listener->mgr->nworkers;
+ for (size_t i = 0; i < nworkers; i++) {
+ isc__netievent__http_eps_t *ievent =
+ isc__nm_get_netievent_httpendpoints(listener->mgr,
+ listener, eps);
+ isc__nm_enqueue_ievent(&listener->mgr->workers[i],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_httpendpoints(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent__http_eps_t *ievent = (isc__netievent__http_eps_t *)ev0;
+ const int tid = isc_nm_tid();
+ isc_nmsocket_t *listener = ievent->sock;
+ isc_nm_http_endpoints_t *eps = ievent->endpoints;
+ UNUSED(worker);
+
+ isc_nm_http_endpoints_detach(&listener->h2.listener_endpoints[tid]);
+ isc_nm_http_endpoints_attach(eps,
+ &listener->h2.listener_endpoints[tid]);
+}
+
+static void
+http_init_listener_endpoints(isc_nmsocket_t *listener,
+ isc_nm_http_endpoints_t *epset) {
+ size_t nworkers;
+
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(VALID_NM(listener->mgr));
+ REQUIRE(VALID_HTTP_ENDPOINTS(epset));
+
+ nworkers = (size_t)listener->mgr->nworkers;
+ INSIST(nworkers > 0);
+
+ listener->h2.listener_endpoints =
+ isc_mem_get(listener->mgr->mctx,
+ sizeof(isc_nm_http_endpoints_t *) * nworkers);
+ listener->h2.n_listener_endpoints = nworkers;
+ for (size_t i = 0; i < nworkers; i++) {
+ listener->h2.listener_endpoints[i] = NULL;
+ isc_nm_http_endpoints_attach(
+ epset, &listener->h2.listener_endpoints[i]);
+ }
+}
+
+static void
+http_cleanup_listener_endpoints(isc_nmsocket_t *listener) {
+ REQUIRE(VALID_NM(listener->mgr));
+
+ if (listener->h2.listener_endpoints == NULL) {
+ return;
+ }
+
+ for (size_t i = 0; i < listener->h2.n_listener_endpoints; i++) {
+ isc_nm_http_endpoints_detach(
+ &listener->h2.listener_endpoints[i]);
+ }
+ isc_mem_put(listener->mgr->mctx, listener->h2.listener_endpoints,
+ sizeof(isc_nm_http_endpoints_t *) *
+ listener->h2.n_listener_endpoints);
+ listener->h2.n_listener_endpoints = 0;
+}
+
+static isc_nm_http_endpoints_t *
+http_get_listener_endpoints(isc_nmsocket_t *listener, const int tid) {
+ isc_nm_http_endpoints_t *eps;
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(tid >= 0);
+ REQUIRE((size_t)tid < listener->h2.n_listener_endpoints);
+
+ eps = listener->h2.listener_endpoints[tid];
+ INSIST(eps != NULL);
+ return (eps);
+}
+
+static const bool base64url_validation_table[256] = {
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, true, false, false, true, true,
+ true, true, true, true, true, true, true, true, false, false,
+ false, false, false, false, false, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true,
+ true, false, false, false, false, true, false, true, true, true,
+ true, true, true, true, true, true, true, true, true, true,
+ true, true, true, true, true, true, true, true, true, true,
+ true, true, true, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false
+};
+
+char *
+isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url,
+ const size_t base64url_len, size_t *res_len) {
+ char *res = NULL;
+ size_t i, k, len;
+
+ if (mem == NULL || base64url == NULL || base64url_len == 0) {
+ return (NULL);
+ }
+
+ len = base64url_len % 4 ? base64url_len + (4 - base64url_len % 4)
+ : base64url_len;
+ res = isc_mem_allocate(mem, len + 1); /* '\0' */
+
+ for (i = 0; i < base64url_len; i++) {
+ switch (base64url[i]) {
+ case '-':
+ res[i] = '+';
+ break;
+ case '_':
+ res[i] = '/';
+ break;
+ default:
+ if (base64url_validation_table[(size_t)base64url[i]]) {
+ res[i] = base64url[i];
+ } else {
+ isc_mem_free(mem, res);
+ return (NULL);
+ }
+ break;
+ }
+ }
+
+ if (base64url_len % 4 != 0) {
+ for (k = 0; k < (4 - base64url_len % 4); k++, i++) {
+ res[i] = '=';
+ }
+ }
+
+ INSIST(i == len);
+
+ if (res_len != NULL) {
+ *res_len = len;
+ }
+
+ res[len] = '\0';
+
+ return (res);
+}
+
+char *
+isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
+ const size_t base64_len, size_t *res_len) {
+ char *res = NULL;
+ size_t i;
+
+ if (mem == NULL || base64 == NULL || base64_len == 0) {
+ return (NULL);
+ }
+
+ res = isc_mem_allocate(mem, base64_len + 1); /* '\0' */
+
+ for (i = 0; i < base64_len; i++) {
+ switch (base64[i]) {
+ case '+':
+ res[i] = '-';
+ break;
+ case '/':
+ res[i] = '_';
+ break;
+ case '=':
+ goto end;
+ break;
+ default:
+ /*
+ * All other characters from the alphabet are the same
+ * for both base64 and base64url, so we can reuse the
+ * validation table for the rest of the characters.
+ */
+ if (base64[i] != '-' && base64[i] != '_' &&
+ base64url_validation_table[(size_t)base64[i]])
+ {
+ res[i] = base64[i];
+ } else {
+ isc_mem_free(mem, res);
+ return (NULL);
+ }
+ break;
+ }
+ }
+end:
+ if (res_len) {
+ *res_len = i;
+ }
+
+ res[i] = '\0';
+
+ return (res);
+}
+
+void
+isc__nm_http_initsocket(isc_nmsocket_t *sock) {
+ REQUIRE(sock != NULL);
+
+ sock->h2 = (isc_nmsocket_h2_t){
+ .request_type = ISC_HTTP_REQ_UNSUPPORTED,
+ .request_scheme = ISC_HTTP_SCHEME_UNSUPPORTED,
+ };
+}
+
+void
+isc__nm_http_cleanup_data(isc_nmsocket_t *sock) {
+ if ((sock->type == isc_nm_tcplistener ||
+ sock->type == isc_nm_tlslistener) &&
+ sock->h2.httpserver != NULL)
+ {
+ isc__nmsocket_detach(&sock->h2.httpserver);
+ }
+
+ if (sock->type == isc_nm_httplistener ||
+ sock->type == isc_nm_httpsocket)
+ {
+ if (sock->type == isc_nm_httplistener &&
+ sock->h2.listener_endpoints != NULL)
+ {
+ /* Delete all handlers */
+ http_cleanup_listener_endpoints(sock);
+ }
+
+ if (sock->h2.request_path != NULL) {
+ isc_mem_free(sock->mgr->mctx, sock->h2.request_path);
+ sock->h2.request_path = NULL;
+ }
+
+ if (sock->h2.query_data != NULL) {
+ isc_mem_free(sock->mgr->mctx, sock->h2.query_data);
+ sock->h2.query_data = NULL;
+ }
+
+ INSIST(sock->h2.connect.cstream == NULL);
+
+ if (isc_buffer_base(&sock->h2.rbuf) != NULL) {
+ void *base = isc_buffer_base(&sock->h2.rbuf);
+ isc_mem_free(sock->mgr->mctx, base);
+ isc_buffer_initnull(&sock->h2.rbuf);
+ }
+ }
+
+ if ((sock->type == isc_nm_httplistener ||
+ sock->type == isc_nm_httpsocket ||
+ sock->type == isc_nm_tcpsocket ||
+ sock->type == isc_nm_tlssocket) &&
+ sock->h2.session != NULL)
+ {
+ if (sock->h2.connect.uri != NULL) {
+ isc_mem_free(sock->mgr->mctx, sock->h2.connect.uri);
+ sock->h2.connect.uri = NULL;
+ }
+ isc__nm_httpsession_detach(&sock->h2.session);
+ }
+}
+
+void
+isc__nm_http_cleartimeout(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_httpsocket);
+
+ sock = handle->sock;
+ if (sock->h2.session != NULL && sock->h2.session->handle != NULL) {
+ INSIST(VALID_HTTP2_SESSION(sock->h2.session));
+ INSIST(VALID_NMHANDLE(sock->h2.session->handle));
+ isc_nmhandle_cleartimeout(sock->h2.session->handle);
+ }
+}
+
+void
+isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_httpsocket);
+
+ sock = handle->sock;
+ if (sock->h2.session != NULL && sock->h2.session->handle != NULL) {
+ INSIST(VALID_HTTP2_SESSION(sock->h2.session));
+ INSIST(VALID_NMHANDLE(sock->h2.session->handle));
+ isc_nmhandle_settimeout(sock->h2.session->handle, timeout);
+ }
+}
+
+void
+isc__nmhandle_http_keepalive(isc_nmhandle_t *handle, bool value) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_httpsocket);
+
+ sock = handle->sock;
+ if (sock->h2.session != NULL && sock->h2.session->handle) {
+ INSIST(VALID_HTTP2_SESSION(sock->h2.session));
+ INSIST(VALID_NMHANDLE(sock->h2.session->handle));
+
+ isc_nmhandle_keepalive(sock->h2.session->handle, value);
+ }
+}
+
+void
+isc_nm_http_makeuri(const bool https, const isc_sockaddr_t *sa,
+ const char *hostname, const uint16_t http_port,
+ const char *abs_path, char *outbuf,
+ const size_t outbuf_len) {
+ char saddr[INET6_ADDRSTRLEN] = { 0 };
+ int family;
+ bool ipv6_addr = false;
+ struct sockaddr_in6 sa6;
+ uint16_t host_port = http_port;
+ const char *host = NULL;
+
+ REQUIRE(outbuf != NULL);
+ REQUIRE(outbuf_len != 0);
+ REQUIRE(isc_nm_http_path_isvalid(abs_path));
+
+ /* If hostname is specified, use that. */
+ if (hostname != NULL && hostname[0] != '\0') {
+ /*
+ * The host name could be an IPv6 address. If so,
+ * wrap it between [ and ].
+ */
+ if (inet_pton(AF_INET6, hostname, &sa6) == 1 &&
+ hostname[0] != '[')
+ {
+ ipv6_addr = true;
+ }
+ host = hostname;
+ } else {
+ /*
+ * A hostname was not specified; build one from
+ * the given IP address.
+ */
+ INSIST(sa != NULL);
+ family = ((const struct sockaddr *)&sa->type.sa)->sa_family;
+ host_port = ntohs(family == AF_INET ? sa->type.sin.sin_port
+ : sa->type.sin6.sin6_port);
+ ipv6_addr = family == AF_INET6;
+ (void)inet_ntop(
+ family,
+ family == AF_INET
+ ? (const struct sockaddr *)&sa->type.sin.sin_addr
+ : (const struct sockaddr *)&sa->type.sin6
+ .sin6_addr,
+ saddr, sizeof(saddr));
+ host = saddr;
+ }
+
+ /*
+ * If the port number was not specified, the default
+ * depends on whether we're using encryption or not.
+ */
+ if (host_port == 0) {
+ host_port = https ? 443 : 80;
+ }
+
+ (void)snprintf(outbuf, outbuf_len, "%s://%s%s%s:%u%s",
+ https ? "https" : "http", ipv6_addr ? "[" : "", host,
+ ipv6_addr ? "]" : "", host_port, abs_path);
+}
+
+/*
+ * DoH GET Query String Scanner-less Recursive Descent Parser/Verifier
+ *
+ * It is based on the following grammar (using WSN/EBNF):
+ *
+ * S = query-string.
+ * query-string = ['?'] { key-value-pair } EOF.
+ * key-value-pair = key '=' value [ '&' ].
+ * key = ('_' | alpha) { '_' | alnum}.
+ * value = value-char {value-char}.
+ * value-char = unreserved-char | percent-charcode.
+ * unreserved-char = alnum |'_' | '.' | '-' | '~'. (* RFC3986, Section 2.3 *)
+ * percent-charcode = '%' hexdigit hexdigit.
+ * ...
+ *
+ * Should be good enough.
+ */
+typedef struct isc_httpparser_state {
+ const char *str;
+
+ const char *last_key;
+ size_t last_key_len;
+
+ const char *last_value;
+ size_t last_value_len;
+
+ bool query_found;
+ const char *query;
+ size_t query_len;
+} isc_httpparser_state_t;
+
+#define MATCH(ch) (st->str[0] == (ch))
+#define MATCH_ALPHA() isalpha((unsigned char)(st->str[0]))
+#define MATCH_DIGIT() isdigit((unsigned char)(st->str[0]))
+#define MATCH_ALNUM() isalnum((unsigned char)(st->str[0]))
+#define MATCH_XDIGIT() isxdigit((unsigned char)(st->str[0]))
+#define ADVANCE() st->str++
+#define GETP() (st->str)
+
+static bool
+rule_query_string(isc_httpparser_state_t *st);
+
+bool
+isc__nm_parse_httpquery(const char *query_string, const char **start,
+ size_t *len) {
+ isc_httpparser_state_t state;
+
+ REQUIRE(start != NULL);
+ REQUIRE(len != NULL);
+
+ if (query_string == NULL || query_string[0] == '\0') {
+ return (false);
+ }
+
+ state = (isc_httpparser_state_t){ .str = query_string };
+ if (!rule_query_string(&state)) {
+ return (false);
+ }
+
+ if (!state.query_found) {
+ return (false);
+ }
+
+ *start = state.query;
+ *len = state.query_len;
+
+ return (true);
+}
+
+static bool
+rule_key_value_pair(isc_httpparser_state_t *st);
+
+static bool
+rule_key(isc_httpparser_state_t *st);
+
+static bool
+rule_value(isc_httpparser_state_t *st);
+
+static bool
+rule_value_char(isc_httpparser_state_t *st);
+
+static bool
+rule_percent_charcode(isc_httpparser_state_t *st);
+
+static bool
+rule_unreserved_char(isc_httpparser_state_t *st);
+
+static bool
+rule_query_string(isc_httpparser_state_t *st) {
+ if (MATCH('?')) {
+ ADVANCE();
+ }
+
+ while (rule_key_value_pair(st)) {
+ /* skip */;
+ }
+
+ if (!MATCH('\0')) {
+ return (false);
+ }
+
+ ADVANCE();
+ return (true);
+}
+
+static bool
+rule_key_value_pair(isc_httpparser_state_t *st) {
+ if (!rule_key(st)) {
+ return (false);
+ }
+
+ if (MATCH('=')) {
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ if (rule_value(st)) {
+ const char dns[] = "dns";
+ if (st->last_key_len == sizeof(dns) - 1 &&
+ memcmp(st->last_key, dns, sizeof(dns) - 1) == 0)
+ {
+ st->query_found = true;
+ st->query = st->last_value;
+ st->query_len = st->last_value_len;
+ }
+ } else {
+ return (false);
+ }
+
+ if (MATCH('&')) {
+ ADVANCE();
+ }
+
+ return (true);
+}
+
+static bool
+rule_key(isc_httpparser_state_t *st) {
+ if (MATCH('_') || MATCH_ALPHA()) {
+ st->last_key = GETP();
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ while (MATCH('_') || MATCH_ALNUM()) {
+ ADVANCE();
+ }
+
+ st->last_key_len = GETP() - st->last_key;
+ return (true);
+}
+
+static bool
+rule_value(isc_httpparser_state_t *st) {
+ const char *s = GETP();
+ if (!rule_value_char(st)) {
+ return (false);
+ }
+
+ st->last_value = s;
+ while (rule_value_char(st)) {
+ /* skip */;
+ }
+ st->last_value_len = GETP() - st->last_value;
+ return (true);
+}
+
+static bool
+rule_value_char(isc_httpparser_state_t *st) {
+ if (rule_unreserved_char(st)) {
+ return (true);
+ }
+
+ return (rule_percent_charcode(st));
+}
+
+static bool
+rule_unreserved_char(isc_httpparser_state_t *st) {
+ if (MATCH_ALNUM() || MATCH('_') || MATCH('.') || MATCH('-') ||
+ MATCH('~'))
+ {
+ ADVANCE();
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+rule_percent_charcode(isc_httpparser_state_t *st) {
+ if (MATCH('%')) {
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ if (!MATCH_XDIGIT()) {
+ return (false);
+ }
+ ADVANCE();
+
+ if (!MATCH_XDIGIT()) {
+ return (false);
+ }
+ ADVANCE();
+
+ return (true);
+}
+
+/*
+ * DoH URL Location Verifier. Based on the following grammar (EBNF/WSN
+ * notation):
+ *
+ * S = path_absolute.
+ * path_absolute = '/' [ segments ] '\0'.
+ * segments = segment_nz { slash_segment }.
+ * slash_segment = '/' segment.
+ * segment = { pchar }.
+ * segment_nz = pchar { pchar }.
+ * pchar = unreserved | pct_encoded | sub_delims | ':' | '@'.
+ * unreserved = ALPHA | DIGIT | '-' | '.' | '_' | '~'.
+ * pct_encoded = '%' XDIGIT XDIGIT.
+ * sub_delims = '!' | '$' | '&' | '\'' | '(' | ')' | '*' | '+' |
+ * ',' | ';' | '='.
+ *
+ * The grammar is extracted from RFC 3986. It is slightly modified to
+ * aid in parser creation, but the end result is the same
+ * (path_absolute is defined slightly differently - split into
+ * multiple productions).
+ *
+ * https://datatracker.ietf.org/doc/html/rfc3986#appendix-A
+ */
+
+typedef struct isc_http_location_parser_state {
+ const char *str;
+} isc_http_location_parser_state_t;
+
+static bool
+rule_loc_path_absolute(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_segments(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_slash_segment(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_segment(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_segment_nz(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_pchar(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_unreserved(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_pct_encoded(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_sub_delims(isc_http_location_parser_state_t *);
+
+static bool
+rule_loc_path_absolute(isc_http_location_parser_state_t *st) {
+ if (MATCH('/')) {
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ (void)rule_loc_segments(st);
+
+ if (MATCH('\0')) {
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ return (true);
+}
+
+static bool
+rule_loc_segments(isc_http_location_parser_state_t *st) {
+ if (!rule_loc_segment_nz(st)) {
+ return (false);
+ }
+
+ while (rule_loc_slash_segment(st)) {
+ /* zero or more */;
+ }
+
+ return (true);
+}
+
+static bool
+rule_loc_slash_segment(isc_http_location_parser_state_t *st) {
+ if (MATCH('/')) {
+ ADVANCE();
+ } else {
+ return (false);
+ }
+
+ return (rule_loc_segment(st));
+}
+
+static bool
+rule_loc_segment(isc_http_location_parser_state_t *st) {
+ while (rule_loc_pchar(st)) {
+ /* zero or more */;
+ }
+
+ return (true);
+}
+
+static bool
+rule_loc_segment_nz(isc_http_location_parser_state_t *st) {
+ if (!rule_loc_pchar(st)) {
+ return (false);
+ }
+
+ while (rule_loc_pchar(st)) {
+ /* zero or more */;
+ }
+
+ return (true);
+}
+
+static bool
+rule_loc_pchar(isc_http_location_parser_state_t *st) {
+ if (rule_loc_unreserved(st)) {
+ return (true);
+ } else if (rule_loc_pct_encoded(st)) {
+ return (true);
+ } else if (rule_loc_sub_delims(st)) {
+ return (true);
+ } else if (MATCH(':') || MATCH('@')) {
+ ADVANCE();
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool
+rule_loc_unreserved(isc_http_location_parser_state_t *st) {
+ if (MATCH_ALPHA() | MATCH_DIGIT() | MATCH('-') | MATCH('.') |
+ MATCH('_') | MATCH('~'))
+ {
+ ADVANCE();
+ return (true);
+ }
+ return (false);
+}
+
+static bool
+rule_loc_pct_encoded(isc_http_location_parser_state_t *st) {
+ if (!MATCH('%')) {
+ return (false);
+ }
+ ADVANCE();
+
+ if (!MATCH_XDIGIT()) {
+ return (false);
+ }
+ ADVANCE();
+
+ if (!MATCH_XDIGIT()) {
+ return (false);
+ }
+ ADVANCE();
+
+ return (true);
+}
+
+static bool
+rule_loc_sub_delims(isc_http_location_parser_state_t *st) {
+ if (MATCH('!') | MATCH('$') | MATCH('&') | MATCH('\'') | MATCH('(') |
+ MATCH(')') | MATCH('*') | MATCH('+') | MATCH(',') | MATCH(';') |
+ MATCH('='))
+ {
+ ADVANCE();
+ return (true);
+ }
+
+ return (false);
+}
+
+bool
+isc_nm_http_path_isvalid(const char *path) {
+ isc_http_location_parser_state_t state = { 0 };
+
+ REQUIRE(path != NULL);
+
+ state.str = path;
+
+ return (rule_loc_path_absolute(&state));
+}
diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h
new file mode 100644
index 0000000..364a933
--- /dev/null
+++ b/lib/isc/netmgr/netmgr-int.h
@@ -0,0 +1,2273 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <unistd.h>
+#include <uv.h>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <isc/astack.h>
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stats.h>
+#include <isc/thread.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include "uv-compat.h"
+
+#define ISC_NETMGR_TID_UNKNOWN -1
+
+/* Must be different from ISC_NETMGR_TID_UNKNOWN */
+#define ISC_NETMGR_NON_INTERLOCKED -2
+
+/*
+ * Receive buffers
+ */
+#if HAVE_DECL_UV_UDP_MMSG_CHUNK
+/*
+ * The value 20 here is UV__MMSG_MAXWIDTH taken from the current libuv source,
+ * libuv will not receive more that 20 datagrams in a single recvmmsg call.
+ */
+#define ISC_NETMGR_UDP_RECVBUF_SIZE (20 * UINT16_MAX)
+#else
+/*
+ * A single DNS message size
+ */
+#define ISC_NETMGR_UDP_RECVBUF_SIZE UINT16_MAX
+#endif
+
+/*
+ * The TCP receive buffer can fit one maximum sized DNS message plus its size,
+ * the receive buffer here affects TCP, DoT and DoH.
+ */
+#define ISC_NETMGR_TCP_RECVBUF_SIZE (sizeof(uint16_t) + UINT16_MAX)
+
+/* Pick the larger buffer */
+#define ISC_NETMGR_RECVBUF_SIZE \
+ (ISC_NETMGR_UDP_RECVBUF_SIZE >= ISC_NETMGR_TCP_RECVBUF_SIZE \
+ ? ISC_NETMGR_UDP_RECVBUF_SIZE \
+ : ISC_NETMGR_TCP_RECVBUF_SIZE)
+
+/*
+ * Send buffer
+ */
+#define ISC_NETMGR_SENDBUF_SIZE (sizeof(uint16_t) + UINT16_MAX)
+
+/*
+ * Make sure our RECVBUF size is large enough
+ */
+
+STATIC_ASSERT(ISC_NETMGR_UDP_RECVBUF_SIZE <= ISC_NETMGR_RECVBUF_SIZE,
+ "UDP receive buffer size must be smaller or equal than worker "
+ "receive buffer size");
+
+STATIC_ASSERT(ISC_NETMGR_TCP_RECVBUF_SIZE <= ISC_NETMGR_RECVBUF_SIZE,
+ "TCP receive buffer size must be smaller or equal than worker "
+ "receive buffer size");
+
+/*%
+ * Regular TCP buffer size.
+ */
+#define NM_REG_BUF 4096
+
+/*%
+ * Larger buffer for when the regular one isn't enough; this will
+ * hold two full DNS packets with lengths. netmgr receives 64k at
+ * most in TCPDNS or TLSDNS connections, so there's no risk of overrun
+ * when using a buffer this size.
+ */
+#define NM_BIG_BUF ISC_NETMGR_TCP_RECVBUF_SIZE * 2
+
+/*%
+ * Maximum segment size (MSS) of TCP socket on which the server responds to
+ * queries. Value lower than common MSS on Ethernet (1220, that is 1280 (IPv6
+ * minimum link MTU) - 40 (IPv6 fixed header) - 20 (TCP fixed header)) will
+ * address path MTU problem.
+ */
+#define NM_MAXSEG (1280 - 20 - 40)
+
+/*
+ * Define NETMGR_TRACE to activate tracing of handles and sockets.
+ * This will impair performance but enables us to quickly determine,
+ * if netmgr resources haven't been cleaned up on shutdown, which ones
+ * are still in use.
+ */
+#ifdef NETMGR_TRACE
+#define TRACE_SIZE 8
+
+void
+isc__nm_dump_active(isc_nm_t *nm);
+
+#if defined(__linux__)
+#include <syscall.h>
+#define gettid() (uint32_t) syscall(SYS_gettid)
+#else
+#define gettid() (uint32_t) pthread_self()
+#endif
+
+#ifdef NETMGR_TRACE_VERBOSE
+#define NETMGR_TRACE_LOG(format, ...) \
+ fprintf(stderr, "%" PRIu32 ":%d:%s:%u:%s:" format, gettid(), \
+ isc_nm_tid(), file, line, func, __VA_ARGS__)
+#else
+#define NETMGR_TRACE_LOG(format, ...) \
+ (void)file; \
+ (void)line; \
+ (void)func;
+#endif
+
+#define FLARG_PASS , file, line, func
+#define FLARG \
+ , const char *file __attribute__((unused)), \
+ unsigned int line __attribute__((unused)), \
+ const char *func __attribute__((unused))
+#define FLARG_IEVENT(ievent) \
+ const char *file = ievent->file; \
+ unsigned int line = ievent->line; \
+ const char *func = ievent->func;
+#define FLARG_IEVENT_PASS(ievent) \
+ ievent->file = file; \
+ ievent->line = line; \
+ ievent->func = func;
+#define isc__nm_uvreq_get(req, sock) \
+ isc___nm_uvreq_get(req, sock, __FILE__, __LINE__, __func__)
+#define isc__nm_uvreq_put(req, sock) \
+ isc___nm_uvreq_put(req, sock, __FILE__, __LINE__, __func__)
+#define isc__nmsocket_init(sock, mgr, type, iface) \
+ isc___nmsocket_init(sock, mgr, type, iface, __FILE__, __LINE__, \
+ __func__)
+#define isc__nmsocket_put(sockp) \
+ isc___nmsocket_put(sockp, __FILE__, __LINE__, __func__)
+#define isc__nmsocket_attach(sock, target) \
+ isc___nmsocket_attach(sock, target, __FILE__, __LINE__, __func__)
+#define isc__nmsocket_detach(socketp) \
+ isc___nmsocket_detach(socketp, __FILE__, __LINE__, __func__)
+#define isc__nmsocket_close(socketp) \
+ isc___nmsocket_close(socketp, __FILE__, __LINE__, __func__)
+#define isc__nmhandle_get(sock, peer, local) \
+ isc___nmhandle_get(sock, peer, local, __FILE__, __LINE__, __func__)
+#define isc__nmsocket_prep_destroy(sock) \
+ isc___nmsocket_prep_destroy(sock, __FILE__, __LINE__, __func__)
+#else
+#define NETMGR_TRACE_LOG(format, ...)
+
+#define FLARG_PASS
+#define FLARG
+#define FLARG_IEVENT(ievent)
+#define FLARG_IEVENT_PASS(ievent)
+#define isc__nm_uvreq_get(req, sock) isc___nm_uvreq_get(req, sock)
+#define isc__nm_uvreq_put(req, sock) isc___nm_uvreq_put(req, sock)
+#define isc__nmsocket_init(sock, mgr, type, iface) \
+ isc___nmsocket_init(sock, mgr, type, iface)
+#define isc__nmsocket_put(sockp) isc___nmsocket_put(sockp)
+#define isc__nmsocket_attach(sock, target) isc___nmsocket_attach(sock, target)
+#define isc__nmsocket_detach(socketp) isc___nmsocket_detach(socketp)
+#define isc__nmsocket_close(socketp) isc___nmsocket_close(socketp)
+#define isc__nmhandle_get(sock, peer, local) \
+ isc___nmhandle_get(sock, peer, local)
+#define isc__nmsocket_prep_destroy(sock) isc___nmsocket_prep_destroy(sock)
+#endif
+
+/*
+ * Queue types in the order of processing priority.
+ */
+typedef enum {
+ NETIEVENT_PRIORITY = 0,
+ NETIEVENT_PRIVILEGED = 1,
+ NETIEVENT_TASK = 2,
+ NETIEVENT_NORMAL = 3,
+ NETIEVENT_MAX = 4,
+} netievent_type_t;
+
+typedef struct isc__nm_uvreq isc__nm_uvreq_t;
+typedef struct isc__netievent isc__netievent_t;
+
+typedef ISC_LIST(isc__netievent_t) isc__netievent_list_t;
+
+typedef struct ievent {
+ isc_mutex_t lock;
+ isc_condition_t cond;
+ isc__netievent_list_t list;
+} ievent_t;
+
+/*
+ * Single network event loop worker.
+ */
+typedef struct isc__networker {
+ isc_nm_t *mgr;
+ int id; /* thread id */
+ uv_loop_t loop; /* libuv loop structure */
+ uv_async_t async; /* async channel to send
+ * data to this networker */
+ bool paused;
+ bool finished;
+ isc_thread_t thread;
+ ievent_t ievents[NETIEVENT_MAX];
+
+ isc_refcount_t references;
+ atomic_int_fast64_t pktcount;
+ char *recvbuf;
+ char *sendbuf;
+ bool recvbuf_inuse;
+} isc__networker_t;
+
+/*
+ * A general handle for a connection bound to a networker. For UDP
+ * connections we have peer address here, so both TCP and UDP can be
+ * handled with a simple send-like function
+ */
+#define NMHANDLE_MAGIC ISC_MAGIC('N', 'M', 'H', 'D')
+#define VALID_NMHANDLE(t) \
+ (ISC_MAGIC_VALID(t, NMHANDLE_MAGIC) && \
+ atomic_load(&(t)->references) > 0)
+
+typedef void (*isc__nm_closecb)(isc_nmhandle_t *);
+typedef struct isc_nm_http_session isc_nm_http_session_t;
+
+struct isc_nmhandle {
+ int magic;
+ isc_refcount_t references;
+
+ /*
+ * The socket is not 'attached' in the traditional
+ * reference-counting sense. Instead, we keep all handles in an
+ * array in the socket object. This way, we don't have circular
+ * dependencies and we can close all handles when we're destroying
+ * the socket.
+ */
+ isc_nmsocket_t *sock;
+
+ isc_nm_http_session_t *httpsession;
+
+ isc_sockaddr_t peer;
+ isc_sockaddr_t local;
+ isc_nm_opaquecb_t doreset; /* reset extra callback, external */
+ isc_nm_opaquecb_t dofree; /* free extra callback, external */
+#ifdef NETMGR_TRACE
+ void *backtrace[TRACE_SIZE];
+ int backtrace_size;
+ LINK(isc_nmhandle_t) active_link;
+#endif
+ void *opaque;
+ char extra[];
+};
+
+typedef enum isc__netievent_type {
+ netievent_udpconnect,
+ netievent_udpclose,
+ netievent_udpsend,
+ netievent_udpread,
+ netievent_udpcancel,
+
+ netievent_routeconnect,
+
+ netievent_tcpconnect,
+ netievent_tcpclose,
+ netievent_tcpsend,
+ netievent_tcpstartread,
+ netievent_tcppauseread,
+ netievent_tcpaccept,
+ netievent_tcpcancel,
+
+ netievent_tcpdnsaccept,
+ netievent_tcpdnsconnect,
+ netievent_tcpdnsclose,
+ netievent_tcpdnssend,
+ netievent_tcpdnsread,
+ netievent_tcpdnscancel,
+
+ netievent_tlsclose,
+ netievent_tlssend,
+ netievent_tlsstartread,
+ netievent_tlsconnect,
+ netievent_tlsdobio,
+ netievent_tlscancel,
+
+ netievent_tlsdnsaccept,
+ netievent_tlsdnsconnect,
+ netievent_tlsdnsclose,
+ netievent_tlsdnssend,
+ netievent_tlsdnsread,
+ netievent_tlsdnscancel,
+ netievent_tlsdnscycle,
+ netievent_tlsdnsshutdown,
+
+ netievent_httpclose,
+ netievent_httpsend,
+ netievent_httpendpoints,
+
+ netievent_shutdown,
+ netievent_stop,
+ netievent_pause,
+
+ netievent_connectcb,
+ netievent_readcb,
+ netievent_sendcb,
+
+ netievent_detach,
+ netievent_close,
+
+ netievent_task,
+ netievent_privilegedtask,
+
+ netievent_settlsctx,
+
+ /*
+ * event type values higher than this will be treated
+ * as high-priority events, which can be processed
+ * while the netmgr is pausing or paused.
+ */
+ netievent_prio = 0xff,
+
+ netievent_udplisten,
+ netievent_udpstop,
+ netievent_tcplisten,
+ netievent_tcpstop,
+ netievent_tcpdnslisten,
+ netievent_tcpdnsstop,
+ netievent_tlsdnslisten,
+ netievent_tlsdnsstop,
+ netievent_sockstop, /* for multilayer sockets */
+
+ netievent_resume,
+} isc__netievent_type;
+
+typedef union {
+ isc_nm_recv_cb_t recv;
+ isc_nm_cb_t send;
+ isc_nm_cb_t connect;
+ isc_nm_accept_cb_t accept;
+} isc__nm_cb_t;
+
+/*
+ * Wrapper around uv_req_t with 'our' fields in it. req->data should
+ * always point to its parent. Note that we always allocate more than
+ * sizeof(struct) because we make room for different req types;
+ */
+#define UVREQ_MAGIC ISC_MAGIC('N', 'M', 'U', 'R')
+#define VALID_UVREQ(t) ISC_MAGIC_VALID(t, UVREQ_MAGIC)
+
+typedef struct isc__nm_uvreq isc__nm_uvreq_t;
+struct isc__nm_uvreq {
+ int magic;
+ isc_nmsocket_t *sock;
+ isc_nmhandle_t *handle;
+ char tcplen[2]; /* The TCP DNS message length */
+ uv_buf_t uvbuf; /* translated isc_region_t, to be
+ * sent or received */
+ isc_sockaddr_t local; /* local address */
+ isc_sockaddr_t peer; /* peer address */
+ isc__nm_cb_t cb; /* callback */
+ void *cbarg; /* callback argument */
+ isc_nm_timer_t *timer; /* TCP write timer */
+ int connect_tries; /* connect retries */
+
+ union {
+ uv_handle_t handle;
+ uv_req_t req;
+ uv_getaddrinfo_t getaddrinfo;
+ uv_getnameinfo_t getnameinfo;
+ uv_shutdown_t shutdown;
+ uv_write_t write;
+ uv_connect_t connect;
+ uv_udp_send_t udp_send;
+ uv_fs_t fs;
+ uv_work_t work;
+ } uv_req;
+ ISC_LINK(isc__nm_uvreq_t) link;
+};
+
+void *
+isc__nm_get_netievent(isc_nm_t *mgr, isc__netievent_type type);
+/*%<
+ * Allocate an ievent and set the type.
+ */
+void
+isc__nm_put_netievent(isc_nm_t *mgr, void *ievent);
+
+/*
+ * The macros here are used to simulate the "inheritance" in C, there's the base
+ * netievent structure that contains just its own type and socket, and there are
+ * extended netievent types that also have handles or requests or other data.
+ *
+ * The macros here ensure that:
+ *
+ * 1. every netievent type has matching definition, declaration and
+ * implementation
+ *
+ * 2. we handle all the netievent types of same subclass the same, e.g. if the
+ * extended netievent contains handle, we always attach to the handle in
+ * the ctor and detach from the handle in dtor.
+ *
+ * There are three macros here for each netievent subclass:
+ *
+ * 1. NETIEVENT_*_TYPE(type) creates the typedef for each type; used below in
+ * this header
+ *
+ * 2. NETIEVENT_*_DECL(type) generates the declaration of the get and put
+ * functions (isc__nm_get_netievent_* and isc__nm_put_netievent_*); used
+ * below in this header
+ *
+ * 3. NETIEVENT_*_DEF(type) generates the definition of the functions; used
+ * either in netmgr.c or matching protocol file (e.g. udp.c, tcp.c, etc.)
+ */
+
+#define NETIEVENT__SOCKET \
+ isc__netievent_type type; \
+ ISC_LINK(isc__netievent_t) link; \
+ isc_nmsocket_t *sock; \
+ const char *file; \
+ unsigned int line; \
+ const char *func;
+
+typedef struct isc__netievent__socket {
+ NETIEVENT__SOCKET;
+} isc__netievent__socket_t;
+
+#define NETIEVENT_SOCKET_TYPE(type) \
+ typedef isc__netievent__socket_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__socket_req {
+ NETIEVENT__SOCKET;
+ isc__nm_uvreq_t *req;
+} isc__netievent__socket_req_t;
+
+#define NETIEVENT_SOCKET_REQ_TYPE(type) \
+ typedef isc__netievent__socket_req_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_REQ_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_REQ_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ ievent->req = req; \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__socket_req_result {
+ NETIEVENT__SOCKET;
+ isc__nm_uvreq_t *req;
+ isc_result_t result;
+} isc__netievent__socket_req_result_t;
+
+#define NETIEVENT_SOCKET_REQ_RESULT_TYPE(type) \
+ typedef isc__netievent__socket_req_result_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_REQ_RESULT_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req, \
+ isc_result_t result); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_REQ_RESULT_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc__nm_uvreq_t *req, \
+ isc_result_t result) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ ievent->req = req; \
+ ievent->result = result; \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__socket_handle {
+ NETIEVENT__SOCKET;
+ isc_nmhandle_t *handle;
+} isc__netievent__socket_handle_t;
+
+#define NETIEVENT_SOCKET_HANDLE_TYPE(type) \
+ typedef isc__netievent__socket_handle_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_HANDLE_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_nmhandle_t *handle); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_HANDLE_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_nmhandle_t *handle) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ isc_nmhandle_attach(handle, &ievent->handle); \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc_nmhandle_detach(&ievent->handle); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__socket_quota {
+ NETIEVENT__SOCKET;
+ isc_quota_t *quota;
+} isc__netievent__socket_quota_t;
+
+#define NETIEVENT_SOCKET_QUOTA_TYPE(type) \
+ typedef isc__netievent__socket_quota_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_QUOTA_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_quota_t *quota); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_QUOTA_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_quota_t *quota) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ ievent->quota = quota; \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__task {
+ isc__netievent_type type;
+ ISC_LINK(isc__netievent_t) link;
+ isc_task_t *task;
+} isc__netievent__task_t;
+
+#define NETIEVENT_TASK_TYPE(type) \
+ typedef isc__netievent__task_t isc__netievent_##type##_t;
+
+#define NETIEVENT_TASK_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_task_t *task); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_TASK_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_task_t *task) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ ievent->task = task; \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ ievent->task = NULL; \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent_udpsend {
+ NETIEVENT__SOCKET;
+ isc_sockaddr_t peer;
+ isc__nm_uvreq_t *req;
+} isc__netievent_udpsend_t;
+
+typedef struct isc__netievent_tlsconnect {
+ NETIEVENT__SOCKET;
+ SSL_CTX *ctx;
+ isc_sockaddr_t local; /* local address */
+ isc_sockaddr_t peer; /* peer address */
+} isc__netievent_tlsconnect_t;
+
+typedef struct isc__netievent {
+ isc__netievent_type type;
+ ISC_LINK(isc__netievent_t) link;
+} isc__netievent_t;
+
+#define NETIEVENT_TYPE(type) typedef isc__netievent_t isc__netievent_##type##_t;
+
+#define NETIEVENT_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type(isc_nm_t *nm); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+typedef struct isc__netievent__tlsctx {
+ NETIEVENT__SOCKET;
+ isc_tlsctx_t *tlsctx;
+} isc__netievent__tlsctx_t;
+
+#define NETIEVENT_SOCKET_TLSCTX_TYPE(type) \
+ typedef isc__netievent__tlsctx_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_TLSCTX_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_TLSCTX_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ isc_tlsctx_attach(tlsctx, &ievent->tlsctx); \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc_tlsctx_free(&ievent->tlsctx); \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+
+#ifdef HAVE_LIBNGHTTP2
+typedef struct isc__netievent__http_eps {
+ NETIEVENT__SOCKET;
+ isc_nm_http_endpoints_t *endpoints;
+} isc__netievent__http_eps_t;
+
+#define NETIEVENT_SOCKET_HTTP_EPS_TYPE(type) \
+ typedef isc__netievent__http_eps_t isc__netievent_##type##_t;
+
+#define NETIEVENT_SOCKET_HTTP_EPS_DECL(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, \
+ isc_nm_http_endpoints_t *endpoints); \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent);
+
+#define NETIEVENT_SOCKET_HTTP_EPS_DEF(type) \
+ isc__netievent_##type##_t *isc__nm_get_netievent_##type( \
+ isc_nm_t *nm, isc_nmsocket_t *sock, \
+ isc_nm_http_endpoints_t *endpoints) { \
+ isc__netievent_##type##_t *ievent = \
+ isc__nm_get_netievent(nm, netievent_##type); \
+ isc__nmsocket_attach(sock, &ievent->sock); \
+ isc_nm_http_endpoints_attach(endpoints, &ievent->endpoints); \
+ \
+ return (ievent); \
+ } \
+ \
+ void isc__nm_put_netievent_##type(isc_nm_t *nm, \
+ isc__netievent_##type##_t *ievent) { \
+ isc_nm_http_endpoints_detach(&ievent->endpoints); \
+ isc__nmsocket_detach(&ievent->sock); \
+ isc__nm_put_netievent(nm, ievent); \
+ }
+#endif /* HAVE_LIBNGHTTP2 */
+
+typedef union {
+ isc__netievent_t ni;
+ isc__netievent__socket_t nis;
+ isc__netievent__socket_req_t nisr;
+ isc__netievent_udpsend_t nius;
+ isc__netievent__socket_quota_t nisq;
+ isc__netievent_tlsconnect_t nitc;
+ isc__netievent__tlsctx_t nitls;
+#ifdef HAVE_LIBNGHTTP2
+ isc__netievent__http_eps_t nihttpeps;
+#endif /* HAVE_LIBNGHTTP2 */
+} isc__netievent_storage_t;
+
+/*
+ * Work item for a uv_work threadpool.
+ */
+typedef struct isc__nm_work {
+ isc_nm_t *netmgr;
+ uv_work_t req;
+ isc_nm_workcb_t cb;
+ isc_nm_after_workcb_t after_cb;
+ void *data;
+} isc__nm_work_t;
+
+/*
+ * Network manager
+ */
+#define NM_MAGIC ISC_MAGIC('N', 'E', 'T', 'M')
+#define VALID_NM(t) ISC_MAGIC_VALID(t, NM_MAGIC)
+
+struct isc_nm {
+ int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ int nworkers;
+ isc_mutex_t lock;
+ isc_condition_t wkstatecond;
+ isc_condition_t wkpausecond;
+ isc__networker_t *workers;
+
+ isc_stats_t *stats;
+
+ uint_fast32_t workers_running;
+ atomic_uint_fast32_t workers_paused;
+ atomic_uint_fast32_t maxudp;
+
+ bool load_balance_sockets;
+
+ atomic_bool paused;
+
+ /*
+ * Active connections are being closed and new connections are
+ * no longer allowed.
+ */
+ atomic_bool closing;
+
+ /*
+ * A worker is actively waiting for other workers, for example to
+ * stop listening; that means no other thread can do the same thing
+ * or pause, or we'll deadlock. We have to either re-enqueue our
+ * event or wait for the other one to finish if we want to pause.
+ */
+ atomic_int interlocked;
+
+ /*
+ * Timeout values for TCP connections, corresponding to
+ * tcp-intiial-timeout, tcp-idle-timeout, tcp-keepalive-timeout,
+ * and tcp-advertised-timeout. Note that these are stored in
+ * milliseconds so they can be used directly with the libuv timer,
+ * but they are configured in tenths of seconds.
+ */
+ atomic_uint_fast32_t init;
+ atomic_uint_fast32_t idle;
+ atomic_uint_fast32_t keepalive;
+ atomic_uint_fast32_t advertised;
+
+ isc_barrier_t pausing;
+ isc_barrier_t resuming;
+
+ /*
+ * Socket SO_RCVBUF and SO_SNDBUF values
+ */
+ atomic_int_fast32_t recv_udp_buffer_size;
+ atomic_int_fast32_t send_udp_buffer_size;
+ atomic_int_fast32_t recv_tcp_buffer_size;
+ atomic_int_fast32_t send_tcp_buffer_size;
+
+#ifdef NETMGR_TRACE
+ ISC_LIST(isc_nmsocket_t) active_sockets;
+#endif
+};
+
+/*%
+ * A universal structure for either a single socket or a group of
+ * dup'd/SO_REUSE_PORT-using sockets listening on the same interface.
+ */
+#define NMSOCK_MAGIC ISC_MAGIC('N', 'M', 'S', 'K')
+#define VALID_NMSOCK(t) ISC_MAGIC_VALID(t, NMSOCK_MAGIC)
+
+/*%
+ * Index into socket stat counter arrays.
+ */
+typedef enum {
+ STATID_OPEN = 0,
+ STATID_OPENFAIL = 1,
+ STATID_CLOSE = 2,
+ STATID_BINDFAIL = 3,
+ STATID_CONNECTFAIL = 4,
+ STATID_CONNECT = 5,
+ STATID_ACCEPTFAIL = 6,
+ STATID_ACCEPT = 7,
+ STATID_SENDFAIL = 8,
+ STATID_RECVFAIL = 9,
+ STATID_ACTIVE = 10,
+ STATID_MAX = 11,
+} isc__nm_statid_t;
+
+#if HAVE_LIBNGHTTP2
+typedef struct isc_nmsocket_tls_send_req {
+ isc_nmsocket_t *tlssock;
+ isc_region_t data;
+ isc_nm_cb_t cb;
+ void *cbarg;
+ isc_nmhandle_t *handle;
+ bool finish;
+ uint8_t smallbuf[512];
+} isc_nmsocket_tls_send_req_t;
+
+typedef enum isc_http_request_type {
+ ISC_HTTP_REQ_GET,
+ ISC_HTTP_REQ_POST,
+ ISC_HTTP_REQ_UNSUPPORTED
+} isc_http_request_type_t;
+
+typedef enum isc_http_scheme_type {
+ ISC_HTTP_SCHEME_HTTP,
+ ISC_HTTP_SCHEME_HTTP_SECURE,
+ ISC_HTTP_SCHEME_UNSUPPORTED
+} isc_http_scheme_type_t;
+
+typedef struct isc_nm_httpcbarg {
+ isc_nm_recv_cb_t cb;
+ void *cbarg;
+ LINK(struct isc_nm_httpcbarg) link;
+} isc_nm_httpcbarg_t;
+
+typedef struct isc_nm_httphandler {
+ char *path;
+ isc_nm_recv_cb_t cb;
+ void *cbarg;
+ size_t extrahandlesize;
+ LINK(struct isc_nm_httphandler) link;
+} isc_nm_httphandler_t;
+
+struct isc_nm_http_endpoints {
+ uint32_t magic;
+ isc_mem_t *mctx;
+
+ ISC_LIST(isc_nm_httphandler_t) handlers;
+ ISC_LIST(isc_nm_httpcbarg_t) handler_cbargs;
+
+ isc_refcount_t references;
+ atomic_bool in_use;
+};
+
+typedef struct isc_nmsocket_h2 {
+ isc_nmsocket_t *psock; /* owner of the structure */
+ char *request_path;
+ char *query_data;
+ size_t query_data_len;
+ bool query_too_large;
+ isc_nm_httphandler_t *handler;
+
+ isc_buffer_t rbuf;
+ isc_buffer_t wbuf;
+
+ int32_t stream_id;
+ isc_nm_http_session_t *session;
+
+ isc_nmsocket_t *httpserver;
+
+ /* maximum concurrent streams (server-side) */
+ atomic_uint_fast32_t max_concurrent_streams;
+
+ uint32_t min_ttl; /* used to set "max-age" in responses */
+
+ isc_http_request_type_t request_type;
+ isc_http_scheme_type_t request_scheme;
+
+ size_t content_length;
+ char clenbuf[128];
+
+ char cache_control_buf[128];
+
+ int headers_error_code;
+ size_t headers_data_processed;
+
+ isc_nm_recv_cb_t cb;
+ void *cbarg;
+ LINK(struct isc_nmsocket_h2) link;
+
+ isc_nm_http_endpoints_t **listener_endpoints;
+ size_t n_listener_endpoints;
+
+ bool response_submitted;
+ struct {
+ char *uri;
+ bool post;
+ isc_tlsctx_t *tlsctx;
+ isc_sockaddr_t local_interface;
+ void *cstream;
+ const char *tls_peer_verify_string;
+ } connect;
+} isc_nmsocket_h2_t;
+#endif /* HAVE_LIBNGHTTP2 */
+
+typedef void (*isc_nm_closehandlecb_t)(void *arg);
+/*%<
+ * Opaque callback function, used for isc_nmhandle 'reset' and 'free'
+ * callbacks.
+ */
+
+struct isc_nmsocket {
+ /*% Unlocked, RO */
+ int magic;
+ int tid;
+ isc_nmsocket_type type;
+ isc_nm_t *mgr;
+
+ /*% Parent socket for multithreaded listeners */
+ isc_nmsocket_t *parent;
+ /*% Listener socket this connection was accepted on */
+ isc_nmsocket_t *listener;
+ /*% Self socket */
+ isc_nmsocket_t *self;
+
+ isc_barrier_t startlistening;
+ isc_barrier_t stoplistening;
+
+ /*% TLS stuff */
+ struct tls {
+ isc_tls_t *tls;
+ isc_tlsctx_t *ctx;
+ isc_tlsctx_client_session_cache_t *client_sess_cache;
+ bool client_session_saved;
+ BIO *app_rbio;
+ BIO *app_wbio;
+ BIO *ssl_rbio;
+ BIO *ssl_wbio;
+ enum {
+ TLS_STATE_NONE,
+ TLS_STATE_HANDSHAKE,
+ TLS_STATE_IO,
+ TLS_STATE_ERROR,
+ TLS_STATE_CLOSING
+ } state;
+ isc_region_t senddata;
+ ISC_LIST(isc__nm_uvreq_t) sendreqs;
+ bool cycle;
+ isc_result_t pending_error;
+ /* List of active send requests. */
+ isc__nm_uvreq_t *pending_req;
+ bool alpn_negotiated;
+ const char *tls_verify_errmsg;
+ } tls;
+
+#if HAVE_LIBNGHTTP2
+ /*% TLS stuff */
+ struct tlsstream {
+ bool server;
+ BIO *bio_in;
+ BIO *bio_out;
+ isc_tls_t *tls;
+ isc_tlsctx_t *ctx;
+ isc_tlsctx_t **listener_tls_ctx; /*%< A context reference per
+ worker */
+ size_t n_listener_tls_ctx;
+ isc_tlsctx_client_session_cache_t *client_sess_cache;
+ bool client_session_saved;
+ isc_nmsocket_t *tlslistener;
+ isc_nmsocket_t *tlssocket;
+ atomic_bool result_updated;
+ enum {
+ TLS_INIT,
+ TLS_HANDSHAKE,
+ TLS_IO,
+ TLS_CLOSED
+ } state; /*%< The order of these is significant */
+ size_t nsending;
+ bool reading;
+ } tlsstream;
+
+ isc_nmsocket_h2_t h2;
+#endif /* HAVE_LIBNGHTTP2 */
+ /*%
+ * quota is the TCP client, attached when a TCP connection
+ * is established. pquota is a non-attached pointer to the
+ * TCP client quota, stored in listening sockets but only
+ * attached in connected sockets.
+ */
+ isc_quota_t *quota;
+ isc_quota_t *pquota;
+ isc_quota_cb_t quotacb;
+
+ /*%
+ * Socket statistics
+ */
+ const isc_statscounter_t *statsindex;
+
+ /*%
+ * TCP read/connect timeout timers.
+ */
+ uv_timer_t read_timer;
+ uint64_t read_timeout;
+ uint64_t connect_timeout;
+
+ /*%
+ * TCP write timeout timer.
+ */
+ uint64_t write_timeout;
+
+ /*% outer socket is for 'wrapped' sockets - e.g. tcpdns in tcp */
+ isc_nmsocket_t *outer;
+
+ /*% server socket for connections */
+ isc_nmsocket_t *server;
+
+ /*% Child sockets for multi-socket setups */
+ isc_nmsocket_t *children;
+ uint_fast32_t nchildren;
+ isc_sockaddr_t iface;
+ isc_nmhandle_t *statichandle;
+ isc_nmhandle_t *outerhandle;
+
+ /*% Extra data allocated at the end of each isc_nmhandle_t */
+ size_t extrahandlesize;
+
+ /*% TCP backlog */
+ int backlog;
+
+ /*% libuv data */
+ uv_os_sock_t fd;
+ union uv_any_handle uv_handle;
+
+ /*% Peer address */
+ isc_sockaddr_t peer;
+
+ /* Atomic */
+ /*% Number of running (e.g. listening) child sockets */
+ atomic_uint_fast32_t rchildren;
+
+ /*%
+ * Socket is active if it's listening, working, etc. If it's
+ * closing, then it doesn't make a sense, for example, to
+ * push handles or reqs for reuse.
+ */
+ atomic_bool active;
+ atomic_bool destroying;
+
+ bool route_sock;
+
+ /*%
+ * Socket is closed if it's not active and all the possible
+ * callbacks were fired, there are no active handles, etc.
+ * If active==false but closed==false, that means the socket
+ * is closing.
+ */
+ atomic_bool closing;
+ atomic_bool closed;
+ atomic_bool listening;
+ atomic_bool connecting;
+ atomic_bool connected;
+ atomic_bool accepting;
+ atomic_bool reading;
+ atomic_bool timedout;
+ isc_refcount_t references;
+
+ /*%
+ * Established an outgoing connection, as client not server.
+ */
+ atomic_bool client;
+
+ /*%
+ * TCPDNS socket has been set not to pipeline.
+ */
+ atomic_bool sequential;
+
+ /*%
+ * The socket is processing read callback, this is guard to not read
+ * data before the readcb is back.
+ */
+ bool processing;
+
+ /*%
+ * A TCP socket has had isc_nm_pauseread() called.
+ */
+ atomic_bool readpaused;
+
+ /*%
+ * A TCP or TCPDNS socket has been set to use the keepalive
+ * timeout instead of the default idle timeout.
+ */
+ atomic_bool keepalive;
+
+ /*%
+ * 'spare' handles for that can be reused to avoid allocations,
+ * for UDP.
+ */
+ isc_astack_t *inactivehandles;
+ isc_astack_t *inactivereqs;
+
+ /*%
+ * Used to wait for TCP listening events to complete, and
+ * for the number of running children to reach zero during
+ * shutdown.
+ *
+ * We use two condition variables to prevent the race where the netmgr
+ * threads would be able to finish and destroy the socket before it's
+ * unlocked by the isc_nm_listen<proto>() function. So, the flow is as
+ * follows:
+ *
+ * 1. parent thread creates all children sockets and passes then to
+ * netthreads, looks at the signaling variable and WAIT(cond) until
+ * the childrens are done initializing
+ *
+ * 2. the events get picked by netthreads, calls the libuv API (and
+ * either succeeds or fails) and WAIT(scond) until all other
+ * children sockets in netthreads are initialized and the listening
+ * socket lock is unlocked
+ *
+ * 3. the control is given back to the parent thread which now either
+ * returns success or shutdowns the listener if an error has
+ * occured in the children netthread
+ *
+ * NOTE: The other approach would be doing an extra attach to the parent
+ * listening socket, and then detach it in the parent thread, but that
+ * breaks the promise that once the libuv socket is initialized on the
+ * nmsocket, the nmsocket needs to be handled only by matching
+ * netthread, so in fact that would add a complexity in a way that
+ * isc__nmsocket_detach would have to be converted to use an
+ * asynchrounous netievent.
+ */
+ isc_mutex_t lock;
+ isc_condition_t cond;
+ isc_condition_t scond;
+
+ /*%
+ * Used to pass a result back from listen or connect events.
+ */
+ isc_result_t result;
+
+ /*%
+ * Current number of active handles.
+ */
+ atomic_int_fast32_t ah;
+
+ /*% Buffer for TCPDNS processing */
+ size_t buf_size;
+ size_t buf_len;
+ unsigned char *buf;
+
+ /*%
+ * This function will be called with handle->sock
+ * as the argument whenever a handle's references drop
+ * to zero, after its reset callback has been called.
+ */
+ isc_nm_closehandlecb_t closehandle_cb;
+
+ isc_nmhandle_t *recv_handle;
+ isc_nm_recv_cb_t recv_cb;
+ void *recv_cbarg;
+ bool recv_read;
+
+ isc_nm_cb_t connect_cb;
+ void *connect_cbarg;
+
+ isc_nm_accept_cb_t accept_cb;
+ void *accept_cbarg;
+
+ atomic_int_fast32_t active_child_connections;
+
+ isc_barrier_t barrier;
+ bool barrier_initialised;
+#ifdef NETMGR_TRACE
+ void *backtrace[TRACE_SIZE];
+ int backtrace_size;
+ LINK(isc_nmsocket_t) active_link;
+ ISC_LIST(isc_nmhandle_t) active_handles;
+#endif
+};
+
+bool
+isc__nm_in_netthread(void);
+/*%<
+ * Returns 'true' if we're in the network thread.
+ */
+
+void
+isc__nm_maybe_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event);
+/*%<
+ * If the caller is already in the matching nmthread, process the netievent
+ * directly, if not enqueue using isc__nm_enqueue_ievent().
+ */
+
+void
+isc__nm_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event);
+/*%<
+ * Enqueue an ievent onto a specific worker queue. (This the only safe
+ * way to use an isc__networker_t from another thread.)
+ */
+
+void
+isc__nm_free_uvbuf(isc_nmsocket_t *sock, const uv_buf_t *buf);
+/*%<
+ * Free a buffer allocated for a receive operation.
+ *
+ * Note that as currently implemented, this doesn't actually
+ * free anything, marks the isc__networker's UDP receive buffer
+ * as "not in use".
+ */
+
+isc_nmhandle_t *
+isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer,
+ isc_sockaddr_t *local FLARG);
+/*%<
+ * Get a handle for the socket 'sock', allocating a new one
+ * if there isn't one available in 'sock->inactivehandles'.
+ *
+ * If 'peer' is not NULL, set the handle's peer address to 'peer',
+ * otherwise set it to 'sock->peer'.
+ *
+ * If 'local' is not NULL, set the handle's local address to 'local',
+ * otherwise set it to 'sock->iface->addr'.
+ *
+ * 'sock' will be attached to 'handle->sock'. The caller may need
+ * to detach the socket afterward.
+ */
+
+isc__nm_uvreq_t *
+isc___nm_uvreq_get(isc_nm_t *mgr, isc_nmsocket_t *sock FLARG);
+/*%<
+ * Get a UV request structure for the socket 'sock', allocating a
+ * new one if there isn't one available in 'sock->inactivereqs'.
+ */
+
+void
+isc___nm_uvreq_put(isc__nm_uvreq_t **req, isc_nmsocket_t *sock FLARG);
+/*%<
+ * Completes the use of a UV request structure, setting '*req' to NULL.
+ *
+ * The UV request is pushed onto the 'sock->inactivereqs' stack or,
+ * if that doesn't work, freed.
+ */
+
+void
+isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
+ isc_sockaddr_t *iface FLARG);
+/*%<
+ * Initialize socket 'sock', attach it to 'mgr', and set it to type 'type'
+ * and its interface to 'iface'.
+ */
+
+void
+isc___nmsocket_attach(isc_nmsocket_t *sock, isc_nmsocket_t **target FLARG);
+/*%<
+ * Attach to a socket, increasing refcount
+ */
+
+void
+isc___nmsocket_detach(isc_nmsocket_t **socketp FLARG);
+/*%<
+ * Detach from socket, decreasing refcount and possibly destroying the
+ * socket if it's no longer referenced.
+ */
+
+void
+isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG);
+/*%<
+ * Market 'sock' as inactive, close it if necessary, and destroy it
+ * if there are no remaining references or active handles.
+ */
+
+void
+isc__nmsocket_shutdown(isc_nmsocket_t *sock);
+/*%<
+ * Initiate the socket shutdown which actively calls the active
+ * callbacks.
+ */
+
+void
+isc__nmsocket_reset(isc_nmsocket_t *sock);
+/*%<
+ * Reset and close the socket.
+ */
+
+bool
+isc__nmsocket_active(isc_nmsocket_t *sock);
+/*%<
+ * Determine whether 'sock' is active by checking 'sock->active'
+ * or, for child sockets, 'sock->parent->active'.
+ */
+
+bool
+isc__nmsocket_deactivate(isc_nmsocket_t *sock);
+/*%<
+ * @brief Deactivate active socket
+ *
+ * Atomically deactive the socket by setting @p sock->active or, for child
+ * sockets, @p sock->parent->active to @c false
+ *
+ * @param[in] sock - valid nmsocket
+ * @return @c false if the socket was already inactive, @c true otherwise
+ */
+
+void
+isc__nmsocket_clearcb(isc_nmsocket_t *sock);
+/*%<
+ * Clear the recv and accept callbacks in 'sock'.
+ */
+
+void
+isc__nmsocket_timer_stop(isc_nmsocket_t *sock);
+void
+isc__nmsocket_timer_start(isc_nmsocket_t *sock);
+void
+isc__nmsocket_timer_restart(isc_nmsocket_t *sock);
+bool
+isc__nmsocket_timer_running(isc_nmsocket_t *sock);
+/*%<
+ * Start/stop/restart/check the timeout on the socket
+ */
+
+void
+isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult, bool async);
+
+void
+isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Issue a connect callback on the socket, used to call the callback
+ */
+
+void
+isc__nm_readcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult);
+void
+isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0);
+
+/*%<
+ * Issue a read callback on the socket, used to call the callback
+ * on failed conditions when the event can't be scheduled on the uv loop.
+ *
+ */
+
+void
+isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult, bool async);
+void
+isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Issue a write callback on the socket, used to call the callback
+ * on failed conditions when the event can't be scheduled on the uv loop.
+ */
+
+void
+isc__nm_async_shutdown(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Walk through all uv handles, get the underlying sockets and issue
+ * close on them.
+ */
+
+void
+isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+/*%<
+ * Back-end implementation of isc_nm_send() for UDP handles.
+ */
+
+void
+isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+/*
+ * Back-end implementation of isc_nm_read() for UDP handles.
+ */
+
+void
+isc__nm_udp_close(isc_nmsocket_t *sock);
+/*%<
+ * Close a UDP socket.
+ */
+
+void
+isc__nm_udp_cancelread(isc_nmhandle_t *handle);
+/*%<
+ * Stop reading on a connected UDP handle.
+ */
+
+void
+isc__nm_udp_shutdown(isc_nmsocket_t *sock);
+/*%<
+ * Called during the shutdown process to close and clean up connected
+ * sockets.
+ */
+
+void
+isc__nm_udp_stoplistening(isc_nmsocket_t *sock);
+/*%<
+ * Stop listening on 'sock'.
+ */
+
+void
+isc__nm_udp_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set or clear the recv timeout for the UDP socket associated with 'handle'.
+ */
+
+void
+isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpstop(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpsend(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handlers for asynchronous UDP events (listen, stoplisten, send).
+ */
+
+void
+isc__nm_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handler for route socket events.
+ */
+
+void
+isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+/*%<
+ * Back-end implementation of isc_nm_send() for TCP handles.
+ */
+
+void
+isc__nm_tcp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+/*
+ * Back-end implementation of isc_nm_read() for TCP handles.
+ */
+
+void
+isc__nm_tcp_close(isc_nmsocket_t *sock);
+/*%<
+ * Close a TCP socket.
+ */
+void
+isc__nm_tcp_pauseread(isc_nmhandle_t *handle);
+/*%<
+ * Pause reading on this handle, while still remembering the callback.
+ */
+
+void
+isc__nm_tcp_resumeread(isc_nmhandle_t *handle);
+/*%<
+ * Resume reading from socket.
+ *
+ */
+
+void
+isc__nm_tcp_shutdown(isc_nmsocket_t *sock);
+/*%<
+ * Called during the shutdown process to close and clean up connected
+ * sockets.
+ */
+
+void
+isc__nm_tcp_cancelread(isc_nmhandle_t *handle);
+/*%<
+ * Stop reading on a connected TCP handle.
+ */
+
+void
+isc__nm_tcp_stoplistening(isc_nmsocket_t *sock);
+/*%<
+ * Stop listening on 'sock'.
+ */
+
+int_fast32_t
+isc__nm_tcp_listener_nactive(isc_nmsocket_t *sock);
+/*%<
+ * Returns the number of active connections for the TCP listener socket.
+ */
+
+void
+isc__nm_tcp_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set the read timeout for the TCP socket associated with 'handle'.
+ */
+
+void
+isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcplisten(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpaccept(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpstop(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpsend(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_startread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_pauseread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpstartread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcppauseread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpcancel(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpclose(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handlers for asynchronous TCP events (connect, listen,
+ * stoplisten, send, read, pause, close).
+ */
+
+void
+isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handlers for asynchronous TLS events.
+ */
+
+void
+isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+/*%<
+ * Back-end implementation of isc_nm_send() for TCPDNS handles.
+ */
+
+void
+isc__nm_tcpdns_shutdown(isc_nmsocket_t *sock);
+
+void
+isc__nm_tcpdns_close(isc_nmsocket_t *sock);
+/*%<
+ * Close a TCPDNS socket.
+ */
+
+void
+isc__nm_tcpdns_stoplistening(isc_nmsocket_t *sock);
+/*%<
+ * Stop listening on 'sock'.
+ */
+
+void
+isc__nm_tcpdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set the read timeout and reset the timer for the TCPDNS socket
+ * associated with 'handle', and the TCP socket it wraps around.
+ */
+
+void
+isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnslisten(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnsclose(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnsstop(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0);
+/*%<
+ * Callback handlers for asynchronous TCPDNS events.
+ */
+
+void
+isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+/*
+ * Back-end implementation of isc_nm_read() for TCPDNS handles.
+ */
+
+void
+isc__nm_tcpdns_cancelread(isc_nmhandle_t *handle);
+/*%<
+ * Stop reading on a connected TCPDNS handle.
+ */
+
+void
+isc__nm_tlsdns_send(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+
+void
+isc__nm_tlsdns_shutdown(isc_nmsocket_t *sock);
+
+void
+isc__nm_tlsdns_close(isc_nmsocket_t *sock);
+/*%<
+ * Close a TLSDNS socket.
+ */
+
+void
+isc__nm_tlsdns_stoplistening(isc_nmsocket_t *sock);
+/*%<
+ * Stop listening on 'sock'.
+ */
+
+void
+isc__nm_tlsdns_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+/*%<
+ * Set the read timeout and reset the timer for the TLSDNS socket
+ * associated with 'handle', and the TCP socket it wraps around.
+ */
+
+void
+isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+/*
+ * Back-end implementation of isc_nm_read() for TLSDNS handles.
+ */
+
+void
+isc__nm_tlsdns_cancelread(isc_nmhandle_t *handle);
+/*%<
+ * Stop reading on a connected TLSDNS handle.
+ */
+
+const char *
+isc__nm_tlsdns_verify_tls_peer_result_string(const isc_nmhandle_t *handle);
+
+void
+isc__nm_async_tlsdnscycle(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnslisten(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsclose(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnssend(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsstop(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0);
+void
+isc__nm_async_tlsdns_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx,
+ const int tid);
+/*%<
+ * Callback handlers for asynchronous TLSDNS events.
+ */
+
+isc_result_t
+isc__nm_tlsdns_xfr_checkperm(isc_nmsocket_t *sock);
+/*%<
+ * Check if it is permitted to do a zone transfer over the given TLSDNS
+ * socket.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS Success, permission check passed successfully
+ * \li #ISC_R_DOTALPNERROR No permission because of ALPN tag mismatch
+ * \li any other result indicates failure (i.e. no permission)
+ *
+ * Requires:
+ * \li 'sock' is a valid TLSDNS socket.
+ */
+
+void
+isc__nm_tlsdns_cleanup_data(isc_nmsocket_t *sock);
+
+#if HAVE_LIBNGHTTP2
+void
+isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+
+void
+isc__nm_tls_cancelread(isc_nmhandle_t *handle);
+
+/*%<
+ * Back-end implementation of isc_nm_send() for TLSDNS handles.
+ */
+
+void
+isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+
+void
+isc__nm_tls_close(isc_nmsocket_t *sock);
+/*%<
+ * Close a TLS socket.
+ */
+
+void
+isc__nm_tls_pauseread(isc_nmhandle_t *handle);
+/*%<
+ * Pause reading on this handle, while still remembering the callback.
+ */
+
+void
+isc__nm_tls_resumeread(isc_nmhandle_t *handle);
+/*%<
+ * Resume reading from the handle.
+ *
+ */
+
+void
+isc__nm_tls_cleanup_data(isc_nmsocket_t *sock);
+
+void
+isc__nm_tls_stoplistening(isc_nmsocket_t *sock);
+
+void
+isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+void
+isc__nm_tls_cleartimeout(isc_nmhandle_t *handle);
+/*%<
+ * Set the read timeout and reset the timer for the socket
+ * associated with 'handle', and the TCP socket it wraps
+ * around.
+ */
+
+const char *
+isc__nm_tls_verify_tls_peer_result_string(const isc_nmhandle_t *handle);
+
+void
+isc__nmhandle_tls_keepalive(isc_nmhandle_t *handle, bool value);
+/*%<
+ * Set the keepalive value on the underlying TCP handle.
+ */
+
+void
+isc__nm_async_tls_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx,
+ const int tid);
+
+void
+isc__nmhandle_tls_setwritetimeout(isc_nmhandle_t *handle,
+ uint64_t write_timeout);
+
+void
+isc__nm_http_stoplistening(isc_nmsocket_t *sock);
+
+void
+isc__nm_http_settimeout(isc_nmhandle_t *handle, uint32_t timeout);
+void
+isc__nm_http_cleartimeout(isc_nmhandle_t *handle);
+/*%<
+ * Set the read timeout and reset the timer for the socket
+ * associated with 'handle', and the TLS/TCP socket it wraps
+ * around.
+ */
+
+void
+isc__nmhandle_http_keepalive(isc_nmhandle_t *handle, bool value);
+/*%<
+ * Set the keepalive value on the underlying session handle
+ */
+
+void
+isc__nm_http_initsocket(isc_nmsocket_t *sock);
+
+void
+isc__nm_http_cleanup_data(isc_nmsocket_t *sock);
+
+isc_result_t
+isc__nm_http_request(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_recv_cb_t reply_cb, void *cbarg);
+
+void
+isc__nm_http_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg);
+
+void
+isc__nm_http_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg);
+
+void
+isc__nm_http_close(isc_nmsocket_t *sock);
+
+void
+isc__nm_http_bad_request(isc_nmhandle_t *handle);
+/*%<
+ * Respond to the request with 400 "Bad Request" status.
+ *
+ * Requires:
+ * \li 'handle' is a valid HTTP netmgr handle object, referencing a server-side
+ * socket
+ */
+
+bool
+isc__nm_http_has_encryption(const isc_nmhandle_t *handle);
+
+void
+isc__nm_http_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl);
+
+const char *
+isc__nm_http_verify_tls_peer_result_string(const isc_nmhandle_t *handle);
+
+void
+isc__nm_async_httpsend(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_httpclose(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_async_httpendpoints(isc__networker_t *worker, isc__netievent_t *ev0);
+
+bool
+isc__nm_parse_httpquery(const char *query_string, const char **start,
+ size_t *len);
+
+char *
+isc__nm_base64url_to_base64(isc_mem_t *mem, const char *base64url,
+ const size_t base64url_len, size_t *res_len);
+
+char *
+isc__nm_base64_to_base64url(isc_mem_t *mem, const char *base64,
+ const size_t base64_len, size_t *res_len);
+
+void
+isc__nm_httpsession_attach(isc_nm_http_session_t *source,
+ isc_nm_http_session_t **targetp);
+void
+isc__nm_httpsession_detach(isc_nm_http_session_t **sessionp);
+
+void
+isc__nm_http_set_tlsctx(isc_nmsocket_t *sock, isc_tlsctx_t *tlsctx);
+
+void
+isc__nm_http_set_max_streams(isc_nmsocket_t *listener,
+ const uint32_t max_concurrent_streams);
+
+#endif
+
+void
+isc__nm_async_settlsctx(isc__networker_t *worker, isc__netievent_t *ev0);
+
+#define isc__nm_uverr2result(x) \
+ isc___nm_uverr2result(x, true, __FILE__, __LINE__, __func__)
+isc_result_t
+isc___nm_uverr2result(int uverr, bool dolog, const char *file,
+ unsigned int line, const char *func);
+/*%<
+ * Convert a libuv error value into an isc_result_t. The
+ * list of supported error values is not complete; new users
+ * of this function should add any expected errors that are
+ * not already there.
+ */
+
+bool
+isc__nm_acquire_interlocked(isc_nm_t *mgr);
+/*%<
+ * Try to acquire interlocked state; return true if successful.
+ */
+
+void
+isc__nm_drop_interlocked(isc_nm_t *mgr);
+/*%<
+ * Drop interlocked state; signal waiters.
+ */
+
+void
+isc__nm_acquire_interlocked_force(isc_nm_t *mgr);
+/*%<
+ * Actively wait for interlocked state.
+ */
+
+void
+isc__nm_async_sockstop(isc__networker_t *worker, isc__netievent_t *ev0);
+
+void
+isc__nm_incstats(isc_nmsocket_t *sock, isc__nm_statid_t id);
+/*%<
+ * Increment socket-related statistics counters.
+ */
+
+void
+isc__nm_decstats(isc_nmsocket_t *sock, isc__nm_statid_t id);
+/*%<
+ * Decrement socket-related statistics counters.
+ */
+
+isc_result_t
+isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp);
+/*%<
+ * Platform independent socket() version
+ */
+
+void
+isc__nm_closesocket(uv_os_sock_t sock);
+/*%<
+ * Platform independent closesocket() version
+ */
+
+isc_result_t
+isc__nm_socket_freebind(uv_os_sock_t fd, sa_family_t sa_family);
+/*%<
+ * Set the IP_FREEBIND (or equivalent) socket option on the uv_handle
+ */
+
+isc_result_t
+isc__nm_socket_reuse(uv_os_sock_t fd);
+/*%<
+ * Set the SO_REUSEADDR or SO_REUSEPORT (or equivalent) socket option on the fd
+ */
+
+isc_result_t
+isc__nm_socket_reuse_lb(uv_os_sock_t fd);
+/*%<
+ * Set the SO_REUSEPORT_LB (or equivalent) socket option on the fd
+ */
+
+isc_result_t
+isc__nm_socket_incoming_cpu(uv_os_sock_t fd);
+/*%<
+ * Set the SO_INCOMING_CPU socket option on the fd if available
+ */
+
+isc_result_t
+isc__nm_socket_disable_pmtud(uv_os_sock_t fd, sa_family_t sa_family);
+/*%<
+ * Disable the Path MTU Discovery, either by disabling IP(V6)_DONTFRAG socket
+ * option, or setting the IP(V6)_MTU_DISCOVER socket option to IP_PMTUDISC_OMIT
+ */
+
+isc_result_t
+isc__nm_socket_v6only(uv_os_sock_t fd, sa_family_t sa_family);
+/*%<
+ * Restrict the socket to sending and receiving IPv6 packets only
+ */
+
+isc_result_t
+isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms);
+/*%<
+ * Set the connection timeout in milliseconds, on non-Linux platforms,
+ * the minimum value must be at least 1000 (1 second).
+ */
+
+isc_result_t
+isc__nm_socket_tcp_nodelay(uv_os_sock_t fd);
+/*%<
+ * Disables Nagle's algorithm on a TCP socket (sets TCP_NODELAY).
+ */
+
+isc_result_t
+isc__nm_socket_tcp_maxseg(uv_os_sock_t fd, int size);
+/*%<
+ * Set the TCP maximum segment size
+ */
+
+isc_result_t
+isc__nm_socket_min_mtu(uv_os_sock_t fd, sa_family_t sa_family);
+/*%<
+ * Use minimum MTU on IPv6 sockets
+ */
+
+void
+isc__nm_set_network_buffers(isc_nm_t *nm, uv_handle_t *handle);
+/*%>
+ * Sets the pre-configured network buffers size on the handle.
+ */
+
+void
+isc__nmsocket_barrier_init(isc_nmsocket_t *listener);
+/*%>
+ * Initialise the socket synchronisation barrier according to the
+ * number of children.
+ */
+
+void
+isc__nmsocket_stop(isc_nmsocket_t *listener);
+/*%>
+ * Broadcast "stop" event for a listener socket across all workers and
+ * wait its processing completion - then, stop and close the underlying
+ * transport listener socket.
+ *
+ * The primitive is used in multi-layer transport listener sockets to
+ * implement shutdown properly: after the broadcasted events has been
+ * processed it is safe to destroy the shared data within the listener
+ * socket (including shutting down the underlying transport listener
+ * socket).
+ */
+
+/*
+ * typedef all the netievent types
+ */
+
+NETIEVENT_SOCKET_TYPE(close);
+NETIEVENT_SOCKET_TYPE(tcpclose);
+NETIEVENT_SOCKET_TYPE(tcplisten);
+NETIEVENT_SOCKET_TYPE(tcppauseread);
+NETIEVENT_SOCKET_TYPE(tcpstop);
+NETIEVENT_SOCKET_TYPE(tlsclose);
+/* NETIEVENT_SOCKET_TYPE(tlsconnect); */ /* unique type, defined independently
+ */
+NETIEVENT_SOCKET_TYPE(tlsdobio);
+NETIEVENT_SOCKET_TYPE(tlsstartread);
+NETIEVENT_SOCKET_HANDLE_TYPE(tlscancel);
+NETIEVENT_SOCKET_TYPE(udpclose);
+NETIEVENT_SOCKET_TYPE(udplisten);
+NETIEVENT_SOCKET_TYPE(udpread);
+/* NETIEVENT_SOCKET_TYPE(udpsend); */ /* unique type, defined independently */
+NETIEVENT_SOCKET_TYPE(udpstop);
+
+NETIEVENT_SOCKET_TYPE(tcpdnsclose);
+NETIEVENT_SOCKET_TYPE(tcpdnsread);
+NETIEVENT_SOCKET_TYPE(tcpdnsstop);
+NETIEVENT_SOCKET_TYPE(tcpdnslisten);
+NETIEVENT_SOCKET_REQ_TYPE(tcpdnsconnect);
+NETIEVENT_SOCKET_REQ_TYPE(tcpdnssend);
+NETIEVENT_SOCKET_HANDLE_TYPE(tcpdnscancel);
+NETIEVENT_SOCKET_QUOTA_TYPE(tcpdnsaccept);
+
+NETIEVENT_SOCKET_TYPE(tlsdnsclose);
+NETIEVENT_SOCKET_TYPE(tlsdnsread);
+NETIEVENT_SOCKET_TYPE(tlsdnsstop);
+NETIEVENT_SOCKET_TYPE(tlsdnsshutdown);
+NETIEVENT_SOCKET_TYPE(tlsdnslisten);
+NETIEVENT_SOCKET_REQ_TYPE(tlsdnsconnect);
+NETIEVENT_SOCKET_REQ_TYPE(tlsdnssend);
+NETIEVENT_SOCKET_HANDLE_TYPE(tlsdnscancel);
+NETIEVENT_SOCKET_QUOTA_TYPE(tlsdnsaccept);
+NETIEVENT_SOCKET_TYPE(tlsdnscycle);
+
+#ifdef HAVE_LIBNGHTTP2
+NETIEVENT_SOCKET_REQ_TYPE(httpsend);
+NETIEVENT_SOCKET_TYPE(httpclose);
+NETIEVENT_SOCKET_HTTP_EPS_TYPE(httpendpoints);
+#endif /* HAVE_LIBNGHTTP2 */
+
+NETIEVENT_SOCKET_REQ_TYPE(tcpconnect);
+NETIEVENT_SOCKET_REQ_TYPE(tcpsend);
+NETIEVENT_SOCKET_TYPE(tcpstartread);
+NETIEVENT_SOCKET_REQ_TYPE(tlssend);
+NETIEVENT_SOCKET_REQ_TYPE(udpconnect);
+
+NETIEVENT_SOCKET_REQ_TYPE(routeconnect);
+
+NETIEVENT_SOCKET_REQ_RESULT_TYPE(connectcb);
+NETIEVENT_SOCKET_REQ_RESULT_TYPE(readcb);
+NETIEVENT_SOCKET_REQ_RESULT_TYPE(sendcb);
+
+NETIEVENT_SOCKET_HANDLE_TYPE(detach);
+NETIEVENT_SOCKET_HANDLE_TYPE(tcpcancel);
+NETIEVENT_SOCKET_HANDLE_TYPE(udpcancel);
+
+NETIEVENT_SOCKET_QUOTA_TYPE(tcpaccept);
+
+NETIEVENT_TYPE(pause);
+NETIEVENT_TYPE(resume);
+NETIEVENT_TYPE(shutdown);
+NETIEVENT_TYPE(stop);
+
+NETIEVENT_TASK_TYPE(task);
+NETIEVENT_TASK_TYPE(privilegedtask);
+
+NETIEVENT_SOCKET_TLSCTX_TYPE(settlsctx);
+NETIEVENT_SOCKET_TYPE(sockstop);
+
+/* Now declared the helper functions */
+
+NETIEVENT_SOCKET_DECL(close);
+NETIEVENT_SOCKET_DECL(tcpclose);
+NETIEVENT_SOCKET_DECL(tcplisten);
+NETIEVENT_SOCKET_DECL(tcppauseread);
+NETIEVENT_SOCKET_DECL(tcpstartread);
+NETIEVENT_SOCKET_DECL(tcpstop);
+NETIEVENT_SOCKET_DECL(tlsclose);
+NETIEVENT_SOCKET_DECL(tlsconnect);
+NETIEVENT_SOCKET_DECL(tlsdobio);
+NETIEVENT_SOCKET_DECL(tlsstartread);
+NETIEVENT_SOCKET_HANDLE_DECL(tlscancel);
+NETIEVENT_SOCKET_DECL(udpclose);
+NETIEVENT_SOCKET_DECL(udplisten);
+NETIEVENT_SOCKET_DECL(udpread);
+NETIEVENT_SOCKET_DECL(udpsend);
+NETIEVENT_SOCKET_DECL(udpstop);
+
+NETIEVENT_SOCKET_DECL(tcpdnsclose);
+NETIEVENT_SOCKET_DECL(tcpdnsread);
+NETIEVENT_SOCKET_DECL(tcpdnsstop);
+NETIEVENT_SOCKET_DECL(tcpdnslisten);
+NETIEVENT_SOCKET_REQ_DECL(tcpdnsconnect);
+NETIEVENT_SOCKET_REQ_DECL(tcpdnssend);
+NETIEVENT_SOCKET_HANDLE_DECL(tcpdnscancel);
+NETIEVENT_SOCKET_QUOTA_DECL(tcpdnsaccept);
+
+NETIEVENT_SOCKET_DECL(tlsdnsclose);
+NETIEVENT_SOCKET_DECL(tlsdnsread);
+NETIEVENT_SOCKET_DECL(tlsdnsstop);
+NETIEVENT_SOCKET_DECL(tlsdnsshutdown);
+NETIEVENT_SOCKET_DECL(tlsdnslisten);
+NETIEVENT_SOCKET_REQ_DECL(tlsdnsconnect);
+NETIEVENT_SOCKET_REQ_DECL(tlsdnssend);
+NETIEVENT_SOCKET_HANDLE_DECL(tlsdnscancel);
+NETIEVENT_SOCKET_QUOTA_DECL(tlsdnsaccept);
+NETIEVENT_SOCKET_DECL(tlsdnscycle);
+
+#ifdef HAVE_LIBNGHTTP2
+NETIEVENT_SOCKET_REQ_DECL(httpsend);
+NETIEVENT_SOCKET_DECL(httpclose);
+NETIEVENT_SOCKET_HTTP_EPS_DECL(httpendpoints);
+#endif /* HAVE_LIBNGHTTP2 */
+
+NETIEVENT_SOCKET_REQ_DECL(tcpconnect);
+NETIEVENT_SOCKET_REQ_DECL(tcpsend);
+NETIEVENT_SOCKET_REQ_DECL(tlssend);
+NETIEVENT_SOCKET_REQ_DECL(udpconnect);
+
+NETIEVENT_SOCKET_REQ_DECL(routeconnect);
+
+NETIEVENT_SOCKET_REQ_RESULT_DECL(connectcb);
+NETIEVENT_SOCKET_REQ_RESULT_DECL(readcb);
+NETIEVENT_SOCKET_REQ_RESULT_DECL(sendcb);
+
+NETIEVENT_SOCKET_HANDLE_DECL(udpcancel);
+NETIEVENT_SOCKET_HANDLE_DECL(tcpcancel);
+NETIEVENT_SOCKET_DECL(detach);
+
+NETIEVENT_SOCKET_QUOTA_DECL(tcpaccept);
+
+NETIEVENT_DECL(pause);
+NETIEVENT_DECL(resume);
+NETIEVENT_DECL(shutdown);
+NETIEVENT_DECL(stop);
+
+NETIEVENT_TASK_DECL(task);
+NETIEVENT_TASK_DECL(privilegedtask);
+
+NETIEVENT_SOCKET_TLSCTX_DECL(settlsctx);
+NETIEVENT_SOCKET_DECL(sockstop);
+
+void
+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result);
+void
+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+ bool async);
+
+isc_result_t
+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock);
+isc_result_t
+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock);
+
+isc__nm_uvreq_t *
+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr);
+
+void
+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf);
+
+void
+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+ const struct sockaddr *addr, unsigned flags);
+void
+isc__nm_tcp_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+void
+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+void
+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf);
+
+isc_result_t
+isc__nm_start_reading(isc_nmsocket_t *sock);
+void
+isc__nm_stop_reading(isc_nmsocket_t *sock);
+isc_result_t
+isc__nm_process_sock_buffer(isc_nmsocket_t *sock);
+void
+isc__nm_resume_processing(void *arg);
+bool
+isc__nmsocket_closing(isc_nmsocket_t *sock);
+bool
+isc__nm_closing(isc_nmsocket_t *sock);
+
+void
+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len);
+
+void
+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_result_t eresult);
+void
+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
+void
+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_result_t eresult, bool async);
+void
+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async);
+
+void
+isc__nm_accept_connection_log(isc_result_t result, bool can_log_quota);
+
+/*
+ * Timeout callbacks
+ */
+void
+isc__nmsocket_connecttimeout_cb(uv_timer_t *timer);
+void
+isc__nmsocket_readtimeout_cb(uv_timer_t *timer);
+void
+isc__nmsocket_writetimeout_cb(void *data, isc_result_t eresult);
+
+#define UV_RUNTIME_CHECK(func, ret) \
+ if (ret != 0) { \
+ FATAL_ERROR("%s failed: %s\n", #func, uv_strerror(ret)); \
+ }
+
+void
+isc__nmsocket_log_tls_session_reuse(isc_nmsocket_t *sock, isc_tls_t *tls);
diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c
new file mode 100644
index 0000000..b19d468
--- /dev/null
+++ b/lib/isc/netmgr/netmgr.c
@@ -0,0 +1,3991 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+#include <isc/backtrace.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/errno.h>
+#include <isc/list.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stats.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+#include "netmgr_p.h"
+#include "openssl_shim.h"
+#include "trampoline_p.h"
+#include "uv-compat.h"
+
+/*%
+ * How many isc_nmhandles and isc_nm_uvreqs will we be
+ * caching for reuse in a socket.
+ */
+#define ISC_NM_HANDLES_STACK_SIZE 600
+#define ISC_NM_REQS_STACK_SIZE 600
+
+/*%
+ * Shortcut index arrays to get access to statistics counters.
+ */
+
+static const isc_statscounter_t udp4statsindex[] = {
+ isc_sockstatscounter_udp4open,
+ isc_sockstatscounter_udp4openfail,
+ isc_sockstatscounter_udp4close,
+ isc_sockstatscounter_udp4bindfail,
+ isc_sockstatscounter_udp4connectfail,
+ isc_sockstatscounter_udp4connect,
+ -1,
+ -1,
+ isc_sockstatscounter_udp4sendfail,
+ isc_sockstatscounter_udp4recvfail,
+ isc_sockstatscounter_udp4active
+};
+
+static const isc_statscounter_t udp6statsindex[] = {
+ isc_sockstatscounter_udp6open,
+ isc_sockstatscounter_udp6openfail,
+ isc_sockstatscounter_udp6close,
+ isc_sockstatscounter_udp6bindfail,
+ isc_sockstatscounter_udp6connectfail,
+ isc_sockstatscounter_udp6connect,
+ -1,
+ -1,
+ isc_sockstatscounter_udp6sendfail,
+ isc_sockstatscounter_udp6recvfail,
+ isc_sockstatscounter_udp6active
+};
+
+static const isc_statscounter_t tcp4statsindex[] = {
+ isc_sockstatscounter_tcp4open, isc_sockstatscounter_tcp4openfail,
+ isc_sockstatscounter_tcp4close, isc_sockstatscounter_tcp4bindfail,
+ isc_sockstatscounter_tcp4connectfail, isc_sockstatscounter_tcp4connect,
+ isc_sockstatscounter_tcp4acceptfail, isc_sockstatscounter_tcp4accept,
+ isc_sockstatscounter_tcp4sendfail, isc_sockstatscounter_tcp4recvfail,
+ isc_sockstatscounter_tcp4active
+};
+
+static const isc_statscounter_t tcp6statsindex[] = {
+ isc_sockstatscounter_tcp6open, isc_sockstatscounter_tcp6openfail,
+ isc_sockstatscounter_tcp6close, isc_sockstatscounter_tcp6bindfail,
+ isc_sockstatscounter_tcp6connectfail, isc_sockstatscounter_tcp6connect,
+ isc_sockstatscounter_tcp6acceptfail, isc_sockstatscounter_tcp6accept,
+ isc_sockstatscounter_tcp6sendfail, isc_sockstatscounter_tcp6recvfail,
+ isc_sockstatscounter_tcp6active
+};
+
+#if 0
+/* XXX: not currently used */
+static const isc_statscounter_t unixstatsindex[] = {
+ isc_sockstatscounter_unixopen,
+ isc_sockstatscounter_unixopenfail,
+ isc_sockstatscounter_unixclose,
+ isc_sockstatscounter_unixbindfail,
+ isc_sockstatscounter_unixconnectfail,
+ isc_sockstatscounter_unixconnect,
+ isc_sockstatscounter_unixacceptfail,
+ isc_sockstatscounter_unixaccept,
+ isc_sockstatscounter_unixsendfail,
+ isc_sockstatscounter_unixrecvfail,
+ isc_sockstatscounter_unixactive
+};
+#endif /* if 0 */
+
+/*
+ * libuv is not thread safe, but has mechanisms to pass messages
+ * between threads. Each socket is owned by a thread. For UDP
+ * sockets we have a set of sockets for each interface and we can
+ * choose a sibling and send the message directly. For TCP, or if
+ * we're calling from a non-networking thread, we need to pass the
+ * request using async_cb.
+ */
+
+static thread_local int isc__nm_tid_v = ISC_NETMGR_TID_UNKNOWN;
+
+static void
+nmsocket_maybe_destroy(isc_nmsocket_t *sock FLARG);
+static void
+nmhandle_free(isc_nmsocket_t *sock, isc_nmhandle_t *handle);
+static isc_threadresult_t
+nm_thread(isc_threadarg_t worker0);
+static void
+async_cb(uv_async_t *handle);
+
+static bool
+process_netievent(isc__networker_t *worker, isc__netievent_t *ievent);
+static isc_result_t
+process_queue(isc__networker_t *worker, netievent_type_t type);
+static void
+wait_for_priority_queue(isc__networker_t *worker);
+static void
+drain_queue(isc__networker_t *worker, netievent_type_t type);
+
+static void
+isc__nm_async_stop(isc__networker_t *worker, isc__netievent_t *ev0);
+static void
+isc__nm_async_pause(isc__networker_t *worker, isc__netievent_t *ev0);
+static void
+isc__nm_async_resume(isc__networker_t *worker, isc__netievent_t *ev0);
+static void
+isc__nm_async_detach(isc__networker_t *worker, isc__netievent_t *ev0);
+static void
+isc__nm_async_close(isc__networker_t *worker, isc__netievent_t *ev0);
+
+static void
+isc__nm_threadpool_initialize(uint32_t workers);
+static void
+isc__nm_work_cb(uv_work_t *req);
+static void
+isc__nm_after_work_cb(uv_work_t *req, int status);
+
+/*%<
+ * Issue a 'handle closed' callback on the socket.
+ */
+
+static void
+nmhandle_detach_cb(isc_nmhandle_t **handlep FLARG);
+
+int
+isc_nm_tid(void) {
+ return (isc__nm_tid_v);
+}
+
+bool
+isc__nm_in_netthread(void) {
+ return (isc__nm_tid_v >= 0);
+}
+
+void
+isc__nm_force_tid(int tid) {
+ isc__nm_tid_v = tid;
+}
+
+static void
+isc__nm_threadpool_initialize(uint32_t workers) {
+ char buf[11];
+ int r = uv_os_getenv("UV_THREADPOOL_SIZE", buf,
+ &(size_t){ sizeof(buf) });
+ if (r == UV_ENOENT) {
+ snprintf(buf, sizeof(buf), "%" PRIu32, workers);
+ uv_os_setenv("UV_THREADPOOL_SIZE", buf);
+ }
+}
+
+#if HAVE_DECL_UV_UDP_LINUX_RECVERR
+#define MINIMAL_UV_VERSION UV_VERSION(1, 42, 0)
+#elif HAVE_DECL_UV_UDP_MMSG_FREE
+#define MINIMAL_UV_VERSION UV_VERSION(1, 40, 0)
+#elif HAVE_DECL_UV_UDP_RECVMMSG
+#define MAXIMAL_UV_VERSION UV_VERSION(1, 39, 99)
+#define MINIMAL_UV_VERSION UV_VERSION(1, 37, 0)
+#else
+#define MAXIMAL_UV_VERSION UV_VERSION(1, 34, 99)
+#define MINIMAL_UV_VERSION UV_VERSION(1, 0, 0)
+#endif
+
+void
+isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netmgrp) {
+ isc_nm_t *mgr = NULL;
+ char name[32];
+
+ REQUIRE(workers > 0);
+
+#ifdef MAXIMAL_UV_VERSION
+ if (uv_version() > MAXIMAL_UV_VERSION) {
+ FATAL_ERROR("libuv version too new: running with libuv %s "
+ "when compiled with libuv %s will lead to "
+ "libuv failures",
+ uv_version_string(), UV_VERSION_STRING);
+ }
+#endif /* MAXIMAL_UV_VERSION */
+
+ if (uv_version() < MINIMAL_UV_VERSION) {
+ FATAL_ERROR("libuv version too old: running with libuv %s "
+ "when compiled with libuv %s will lead to "
+ "libuv failures",
+ uv_version_string(), UV_VERSION_STRING);
+ }
+
+ isc__nm_threadpool_initialize(workers);
+
+ mgr = isc_mem_get(mctx, sizeof(*mgr));
+ *mgr = (isc_nm_t){ .nworkers = workers };
+
+ isc_mem_attach(mctx, &mgr->mctx);
+ isc_mutex_init(&mgr->lock);
+ isc_condition_init(&mgr->wkstatecond);
+ isc_condition_init(&mgr->wkpausecond);
+ isc_refcount_init(&mgr->references, 1);
+ atomic_init(&mgr->maxudp, 0);
+ atomic_init(&mgr->interlocked, ISC_NETMGR_NON_INTERLOCKED);
+ atomic_init(&mgr->workers_paused, 0);
+ atomic_init(&mgr->paused, false);
+ atomic_init(&mgr->closing, false);
+ atomic_init(&mgr->recv_tcp_buffer_size, 0);
+ atomic_init(&mgr->send_tcp_buffer_size, 0);
+ atomic_init(&mgr->recv_udp_buffer_size, 0);
+ atomic_init(&mgr->send_udp_buffer_size, 0);
+#if HAVE_SO_REUSEPORT_LB
+ mgr->load_balance_sockets = true;
+#else
+ mgr->load_balance_sockets = false;
+#endif
+
+#ifdef NETMGR_TRACE
+ ISC_LIST_INIT(mgr->active_sockets);
+#endif
+
+ /*
+ * Default TCP timeout values.
+ * May be updated by isc_nm_tcptimeouts().
+ */
+ atomic_init(&mgr->init, 30000);
+ atomic_init(&mgr->idle, 30000);
+ atomic_init(&mgr->keepalive, 30000);
+ atomic_init(&mgr->advertised, 30000);
+
+ isc_barrier_init(&mgr->pausing, workers);
+ isc_barrier_init(&mgr->resuming, workers);
+
+ mgr->workers = isc_mem_get(mctx, workers * sizeof(isc__networker_t));
+ for (size_t i = 0; i < workers; i++) {
+ isc__networker_t *worker = &mgr->workers[i];
+ int r;
+
+ *worker = (isc__networker_t){
+ .mgr = mgr,
+ .id = i,
+ };
+
+ r = uv_loop_init(&worker->loop);
+ UV_RUNTIME_CHECK(uv_loop_init, r);
+
+ worker->loop.data = &mgr->workers[i];
+
+ r = uv_async_init(&worker->loop, &worker->async, async_cb);
+ UV_RUNTIME_CHECK(uv_async_init, r);
+
+ for (size_t type = 0; type < NETIEVENT_MAX; type++) {
+ isc_mutex_init(&worker->ievents[type].lock);
+ isc_condition_init(&worker->ievents[type].cond);
+ ISC_LIST_INIT(worker->ievents[type].list);
+ }
+
+ worker->recvbuf = isc_mem_get(mctx, ISC_NETMGR_RECVBUF_SIZE);
+ worker->sendbuf = isc_mem_get(mctx, ISC_NETMGR_SENDBUF_SIZE);
+
+ /*
+ * We need to do this here and not in nm_thread to avoid a
+ * race - we could exit isc_nm_start, launch nm_destroy,
+ * and nm_thread would still not be up.
+ */
+ mgr->workers_running++;
+ isc_thread_create(nm_thread, &mgr->workers[i], &worker->thread);
+
+ snprintf(name, sizeof(name), "isc-net-%04zu", i);
+ isc_thread_setname(worker->thread, name);
+ }
+
+ mgr->magic = NM_MAGIC;
+ *netmgrp = mgr;
+}
+
+/*
+ * Free the resources of the network manager.
+ */
+static void
+nm_destroy(isc_nm_t **mgr0) {
+ REQUIRE(VALID_NM(*mgr0));
+ REQUIRE(!isc__nm_in_netthread());
+
+ isc_nm_t *mgr = *mgr0;
+ *mgr0 = NULL;
+
+ isc_refcount_destroy(&mgr->references);
+
+ mgr->magic = 0;
+
+ for (int i = 0; i < mgr->nworkers; i++) {
+ isc__networker_t *worker = &mgr->workers[i];
+ isc__netievent_t *event = isc__nm_get_netievent_stop(mgr);
+ isc__nm_enqueue_ievent(worker, event);
+ }
+
+ LOCK(&mgr->lock);
+ while (mgr->workers_running > 0) {
+ WAIT(&mgr->wkstatecond, &mgr->lock);
+ }
+ UNLOCK(&mgr->lock);
+
+ for (int i = 0; i < mgr->nworkers; i++) {
+ isc__networker_t *worker = &mgr->workers[i];
+ int r;
+
+ r = uv_loop_close(&worker->loop);
+ UV_RUNTIME_CHECK(uv_loop_close, r);
+
+ for (size_t type = 0; type < NETIEVENT_MAX; type++) {
+ INSIST(ISC_LIST_EMPTY(worker->ievents[type].list));
+ isc_condition_destroy(&worker->ievents[type].cond);
+ isc_mutex_destroy(&worker->ievents[type].lock);
+ }
+
+ isc_mem_put(mgr->mctx, worker->sendbuf,
+ ISC_NETMGR_SENDBUF_SIZE);
+ isc_mem_put(mgr->mctx, worker->recvbuf,
+ ISC_NETMGR_RECVBUF_SIZE);
+ isc_thread_join(worker->thread, NULL);
+ }
+
+ if (mgr->stats != NULL) {
+ isc_stats_detach(&mgr->stats);
+ }
+
+ isc_barrier_destroy(&mgr->resuming);
+ isc_barrier_destroy(&mgr->pausing);
+
+ isc_condition_destroy(&mgr->wkstatecond);
+ isc_condition_destroy(&mgr->wkpausecond);
+ isc_mutex_destroy(&mgr->lock);
+
+ isc_mem_put(mgr->mctx, mgr->workers,
+ mgr->nworkers * sizeof(isc__networker_t));
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr));
+}
+
+static void
+enqueue_pause(isc__networker_t *worker) {
+ isc__netievent_pause_t *event =
+ isc__nm_get_netievent_pause(worker->mgr);
+ isc__nm_enqueue_ievent(worker, (isc__netievent_t *)event);
+}
+
+static void
+isc__nm_async_pause(isc__networker_t *worker, isc__netievent_t *ev0) {
+ UNUSED(ev0);
+ REQUIRE(worker->paused == false);
+
+ worker->paused = true;
+ uv_stop(&worker->loop);
+}
+
+void
+isc_nm_pause(isc_nm_t *mgr) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(!atomic_load(&mgr->paused));
+
+ isc__nm_acquire_interlocked_force(mgr);
+
+ if (isc__nm_in_netthread()) {
+ REQUIRE(isc_nm_tid() == 0);
+ }
+
+ for (int i = 0; i < mgr->nworkers; i++) {
+ isc__networker_t *worker = &mgr->workers[i];
+ if (i == isc_nm_tid()) {
+ isc__nm_async_pause(worker, NULL);
+ } else {
+ enqueue_pause(worker);
+ }
+ }
+
+ if (isc__nm_in_netthread()) {
+ atomic_fetch_add(&mgr->workers_paused, 1);
+ isc_barrier_wait(&mgr->pausing);
+ }
+
+ LOCK(&mgr->lock);
+ while (atomic_load(&mgr->workers_paused) != mgr->workers_running) {
+ WAIT(&mgr->wkstatecond, &mgr->lock);
+ }
+ UNLOCK(&mgr->lock);
+
+ atomic_compare_exchange_enforced(&mgr->paused, &(bool){ false }, true);
+}
+
+static void
+enqueue_resume(isc__networker_t *worker) {
+ isc__netievent_resume_t *event =
+ isc__nm_get_netievent_resume(worker->mgr);
+ isc__nm_enqueue_ievent(worker, (isc__netievent_t *)event);
+}
+
+static void
+isc__nm_async_resume(isc__networker_t *worker, isc__netievent_t *ev0) {
+ UNUSED(ev0);
+ REQUIRE(worker->paused == true);
+
+ worker->paused = false;
+}
+
+void
+isc_nm_resume(isc_nm_t *mgr) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(atomic_load(&mgr->paused));
+
+ if (isc__nm_in_netthread()) {
+ REQUIRE(isc_nm_tid() == 0);
+ drain_queue(&mgr->workers[isc_nm_tid()], NETIEVENT_PRIORITY);
+ }
+
+ for (int i = 0; i < mgr->nworkers; i++) {
+ isc__networker_t *worker = &mgr->workers[i];
+ if (i == isc_nm_tid()) {
+ isc__nm_async_resume(worker, NULL);
+ } else {
+ enqueue_resume(worker);
+ }
+ }
+
+ if (isc__nm_in_netthread()) {
+ drain_queue(&mgr->workers[isc_nm_tid()], NETIEVENT_PRIVILEGED);
+
+ atomic_fetch_sub(&mgr->workers_paused, 1);
+ isc_barrier_wait(&mgr->resuming);
+ }
+
+ LOCK(&mgr->lock);
+ while (atomic_load(&mgr->workers_paused) != 0) {
+ WAIT(&mgr->wkstatecond, &mgr->lock);
+ }
+ UNLOCK(&mgr->lock);
+
+ atomic_compare_exchange_enforced(&mgr->paused, &(bool){ true }, false);
+
+ isc__nm_drop_interlocked(mgr);
+}
+
+void
+isc_nm_attach(isc_nm_t *mgr, isc_nm_t **dst) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(dst != NULL && *dst == NULL);
+
+ isc_refcount_increment(&mgr->references);
+
+ *dst = mgr;
+}
+
+void
+isc_nm_detach(isc_nm_t **mgr0) {
+ isc_nm_t *mgr = NULL;
+
+ REQUIRE(mgr0 != NULL);
+ REQUIRE(VALID_NM(*mgr0));
+
+ mgr = *mgr0;
+ *mgr0 = NULL;
+
+ if (isc_refcount_decrement(&mgr->references) == 1) {
+ nm_destroy(&mgr);
+ }
+}
+
+void
+isc__netmgr_shutdown(isc_nm_t *mgr) {
+ REQUIRE(VALID_NM(mgr));
+
+ atomic_store(&mgr->closing, true);
+ for (int i = 0; i < mgr->nworkers; i++) {
+ isc__netievent_t *event = NULL;
+ event = isc__nm_get_netievent_shutdown(mgr);
+ isc__nm_enqueue_ievent(&mgr->workers[i], event);
+ }
+}
+
+void
+isc__netmgr_destroy(isc_nm_t **netmgrp) {
+ isc_nm_t *mgr = NULL;
+ int counter = 0;
+
+ REQUIRE(VALID_NM(*netmgrp));
+
+ mgr = *netmgrp;
+
+ /*
+ * Close active connections.
+ */
+ isc__netmgr_shutdown(mgr);
+
+ /*
+ * Wait for the manager to be dereferenced elsewhere.
+ */
+ while (isc_refcount_current(&mgr->references) > 1 && counter++ < 1000) {
+ uv_sleep(10);
+ }
+
+#ifdef NETMGR_TRACE
+ if (isc_refcount_current(&mgr->references) > 1) {
+ isc__nm_dump_active(mgr);
+ UNREACHABLE();
+ }
+#endif
+
+ /*
+ * Now just patiently wait
+ */
+ while (isc_refcount_current(&mgr->references) > 1) {
+ uv_sleep(10);
+ }
+
+ /*
+ * Detach final reference.
+ */
+ isc_nm_detach(netmgrp);
+}
+
+void
+isc_nm_maxudp(isc_nm_t *mgr, uint32_t maxudp) {
+ REQUIRE(VALID_NM(mgr));
+
+ atomic_store(&mgr->maxudp, maxudp);
+}
+
+void
+isc_nmhandle_setwritetimeout(isc_nmhandle_t *handle, uint64_t write_timeout) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->tid == isc_nm_tid());
+
+ switch (handle->sock->type) {
+ case isc_nm_tcpsocket:
+ case isc_nm_udpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ handle->sock->write_timeout = write_timeout;
+ break;
+#ifdef HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nmhandle_tls_setwritetimeout(handle, write_timeout);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+void
+isc_nm_settimeouts(isc_nm_t *mgr, uint32_t init, uint32_t idle,
+ uint32_t keepalive, uint32_t advertised) {
+ REQUIRE(VALID_NM(mgr));
+
+ atomic_store(&mgr->init, init);
+ atomic_store(&mgr->idle, idle);
+ atomic_store(&mgr->keepalive, keepalive);
+ atomic_store(&mgr->advertised, advertised);
+}
+
+void
+isc_nm_setnetbuffers(isc_nm_t *mgr, int32_t recv_tcp, int32_t send_tcp,
+ int32_t recv_udp, int32_t send_udp) {
+ REQUIRE(VALID_NM(mgr));
+
+ atomic_store(&mgr->recv_tcp_buffer_size, recv_tcp);
+ atomic_store(&mgr->send_tcp_buffer_size, send_tcp);
+ atomic_store(&mgr->recv_udp_buffer_size, recv_udp);
+ atomic_store(&mgr->send_udp_buffer_size, send_udp);
+}
+
+bool
+isc_nm_getloadbalancesockets(isc_nm_t *mgr) {
+ REQUIRE(VALID_NM(mgr));
+
+ return (mgr->load_balance_sockets);
+}
+
+void
+isc_nm_setloadbalancesockets(isc_nm_t *mgr, bool enabled) {
+ REQUIRE(VALID_NM(mgr));
+
+#if HAVE_SO_REUSEPORT_LB
+ mgr->load_balance_sockets = enabled;
+#else
+ UNUSED(enabled);
+#endif
+}
+
+void
+isc_nm_gettimeouts(isc_nm_t *mgr, uint32_t *initial, uint32_t *idle,
+ uint32_t *keepalive, uint32_t *advertised) {
+ REQUIRE(VALID_NM(mgr));
+
+ if (initial != NULL) {
+ *initial = atomic_load(&mgr->init);
+ }
+
+ if (idle != NULL) {
+ *idle = atomic_load(&mgr->idle);
+ }
+
+ if (keepalive != NULL) {
+ *keepalive = atomic_load(&mgr->keepalive);
+ }
+
+ if (advertised != NULL) {
+ *advertised = atomic_load(&mgr->advertised);
+ }
+}
+
+/*
+ * nm_thread is a single worker thread, that runs uv_run event loop
+ * until asked to stop.
+ *
+ * There are four queues for asynchronous events:
+ *
+ * 1. priority queue - netievents on the priority queue are run even when
+ * the taskmgr enters exclusive mode and the netmgr is paused. This
+ * is needed to properly start listening on the interfaces, free
+ * resources on shutdown, or resume from a pause.
+ *
+ * 2. privileged task queue - only privileged tasks are queued here and
+ * this is the first queue that gets processed when network manager
+ * is unpaused using isc_nm_resume(). All netmgr workers need to
+ * clean the privileged task queue before they all proceed to normal
+ * operation. Both task queues are processed when the workers are
+ * shutting down.
+ *
+ * 3. task queue - only (traditional) tasks are scheduled here, and this
+ * queue and the privileged task queue are both processed when the
+ * netmgr workers are finishing. This is needed to process the task
+ * shutdown events.
+ *
+ * 4. normal queue - this is the queue with netmgr events, e.g. reading,
+ * sending, callbacks, etc.
+ */
+
+static isc_threadresult_t
+nm_thread(isc_threadarg_t worker0) {
+ isc__networker_t *worker = (isc__networker_t *)worker0;
+ isc_nm_t *mgr = worker->mgr;
+
+ isc__nm_tid_v = worker->id;
+
+ while (true) {
+ /*
+ * uv_run() runs async_cb() in a loop, which processes
+ * all four event queues until a "pause" or "stop" event
+ * is encountered. On pause, we process only priority and
+ * privileged events until resuming.
+ */
+ int r = uv_run(&worker->loop, UV_RUN_DEFAULT);
+ INSIST(r > 0 || worker->finished);
+
+ if (worker->paused) {
+ INSIST(atomic_load(&mgr->interlocked) != isc_nm_tid());
+
+ atomic_fetch_add(&mgr->workers_paused, 1);
+ if (isc_barrier_wait(&mgr->pausing) != 0) {
+ LOCK(&mgr->lock);
+ SIGNAL(&mgr->wkstatecond);
+ UNLOCK(&mgr->lock);
+ }
+
+ while (worker->paused) {
+ wait_for_priority_queue(worker);
+ }
+
+ /*
+ * All workers must drain the privileged event
+ * queue before we resume from pause.
+ */
+ drain_queue(worker, NETIEVENT_PRIVILEGED);
+
+ atomic_fetch_sub(&mgr->workers_paused, 1);
+ if (isc_barrier_wait(&mgr->resuming) != 0) {
+ LOCK(&mgr->lock);
+ SIGNAL(&mgr->wkstatecond);
+ UNLOCK(&mgr->lock);
+ }
+ }
+
+ if (r == 0) {
+ INSIST(worker->finished);
+ break;
+ }
+
+ INSIST(!worker->finished);
+ }
+
+ /*
+ * We are shutting down. Drain the queues.
+ */
+ drain_queue(worker, NETIEVENT_PRIVILEGED);
+ drain_queue(worker, NETIEVENT_TASK);
+
+ for (size_t type = 0; type < NETIEVENT_MAX; type++) {
+ LOCK(&worker->ievents[type].lock);
+ INSIST(ISC_LIST_EMPTY(worker->ievents[type].list));
+ UNLOCK(&worker->ievents[type].lock);
+ }
+
+ LOCK(&mgr->lock);
+ mgr->workers_running--;
+ SIGNAL(&mgr->wkstatecond);
+ UNLOCK(&mgr->lock);
+
+ return ((isc_threadresult_t)0);
+}
+
+static bool
+process_all_queues(isc__networker_t *worker) {
+ bool reschedule = false;
+ /*
+ * The queue processing functions will return false when the
+ * system is pausing or stopping and we don't want to process
+ * the other queues in such case, but we need the async event
+ * to be rescheduled in the next uv_run().
+ */
+ for (size_t type = 0; type < NETIEVENT_MAX; type++) {
+ isc_result_t result = process_queue(worker, type);
+ switch (result) {
+ case ISC_R_SUSPEND:
+ reschedule = true;
+ break;
+ case ISC_R_EMPTY:
+ /* empty queue */
+ break;
+ case ISC_R_SUCCESS:
+ reschedule = true;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ return (reschedule);
+}
+
+/*
+ * async_cb() is a universal callback for 'async' events sent to event loop.
+ * It's the only way to safely pass data to the libuv event loop. We use a
+ * single async event and a set of lockless queues of 'isc__netievent_t'
+ * structures passed from other threads.
+ */
+static void
+async_cb(uv_async_t *handle) {
+ isc__networker_t *worker = (isc__networker_t *)handle->loop->data;
+
+ if (process_all_queues(worker)) {
+ /*
+ * If we didn't process all the events, we need to enqueue
+ * async_cb to be run in the next iteration of the uv_loop
+ */
+ uv_async_send(handle);
+ }
+}
+
+static void
+isc__nm_async_stop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ UNUSED(ev0);
+ worker->finished = true;
+ /* Close the async handler */
+ uv_close((uv_handle_t *)&worker->async, NULL);
+}
+
+void
+isc_nm_task_enqueue(isc_nm_t *nm, isc_task_t *task, int threadid) {
+ isc__netievent_t *event = NULL;
+ int tid;
+ isc__networker_t *worker = NULL;
+
+ if (threadid == -1) {
+ tid = (int)isc_random_uniform(nm->nworkers);
+ } else {
+ tid = threadid % nm->nworkers;
+ }
+
+ worker = &nm->workers[tid];
+
+ if (isc_task_privileged(task)) {
+ event = (isc__netievent_t *)
+ isc__nm_get_netievent_privilegedtask(nm, task);
+ } else {
+ event = (isc__netievent_t *)isc__nm_get_netievent_task(nm,
+ task);
+ }
+
+ isc__nm_enqueue_ievent(worker, event);
+}
+
+#define isc__nm_async_privilegedtask(worker, ev0) \
+ isc__nm_async_task(worker, ev0)
+
+static void
+isc__nm_async_task(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_task_t *ievent = (isc__netievent_task_t *)ev0;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ result = isc_task_run(ievent->task);
+
+ /*
+ * Tasks can block for a long time, especially when used by tools in
+ * interactive mode. Update the event loop's time to avoid unexpected
+ * errors when processing later events during the same callback.
+ * For example, newly started timers can fire too early, because the
+ * current time was stale. See the note about uv_update_time() in the
+ * https://docs.libuv.org/en/v1.x/timer.html#c.uv_timer_start page.
+ */
+ uv_update_time(&worker->loop);
+
+ switch (result) {
+ case ISC_R_QUOTA:
+ isc_task_ready(ievent->task);
+ return;
+ case ISC_R_SUCCESS:
+ return;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+wait_for_priority_queue(isc__networker_t *worker) {
+ isc_condition_t *cond = &worker->ievents[NETIEVENT_PRIORITY].cond;
+ isc_mutex_t *lock = &worker->ievents[NETIEVENT_PRIORITY].lock;
+ isc__netievent_list_t *list =
+ &(worker->ievents[NETIEVENT_PRIORITY].list);
+
+ LOCK(lock);
+ while (ISC_LIST_EMPTY(*list)) {
+ WAIT(cond, lock);
+ }
+ UNLOCK(lock);
+
+ drain_queue(worker, NETIEVENT_PRIORITY);
+}
+
+static void
+drain_queue(isc__networker_t *worker, netievent_type_t type) {
+ bool empty = false;
+ while (!empty) {
+ if (process_queue(worker, type) == ISC_R_EMPTY) {
+ LOCK(&worker->ievents[type].lock);
+ empty = ISC_LIST_EMPTY(worker->ievents[type].list);
+ UNLOCK(&worker->ievents[type].lock);
+ }
+ }
+}
+
+/*
+ * The two macros here generate the individual cases for the process_netievent()
+ * function. The NETIEVENT_CASE(type) macro is the common case, and
+ * NETIEVENT_CASE_NOMORE(type) is a macro that causes the loop in the
+ * process_queue() to stop, e.g. it's only used for the netievent that
+ * stops/pauses processing the enqueued netievents.
+ */
+#define NETIEVENT_CASE(type) \
+ case netievent_##type: { \
+ isc__nm_async_##type(worker, ievent); \
+ isc__nm_put_netievent_##type( \
+ worker->mgr, (isc__netievent_##type##_t *)ievent); \
+ return (true); \
+ }
+
+#define NETIEVENT_CASE_NOMORE(type) \
+ case netievent_##type: { \
+ isc__nm_async_##type(worker, ievent); \
+ isc__nm_put_netievent_##type(worker->mgr, ievent); \
+ return (false); \
+ }
+
+static bool
+process_netievent(isc__networker_t *worker, isc__netievent_t *ievent) {
+ REQUIRE(worker->id == isc_nm_tid());
+
+ switch (ievent->type) {
+ /* Don't process more ievents when we are stopping */
+ NETIEVENT_CASE_NOMORE(stop);
+
+ NETIEVENT_CASE(privilegedtask);
+ NETIEVENT_CASE(task);
+
+ NETIEVENT_CASE(udpconnect);
+ NETIEVENT_CASE(udplisten);
+ NETIEVENT_CASE(udpstop);
+ NETIEVENT_CASE(udpsend);
+ NETIEVENT_CASE(udpread);
+ NETIEVENT_CASE(udpcancel);
+ NETIEVENT_CASE(udpclose);
+
+ NETIEVENT_CASE(routeconnect);
+
+ NETIEVENT_CASE(tcpaccept);
+ NETIEVENT_CASE(tcpconnect);
+ NETIEVENT_CASE(tcplisten);
+ NETIEVENT_CASE(tcpstartread);
+ NETIEVENT_CASE(tcppauseread);
+ NETIEVENT_CASE(tcpsend);
+ NETIEVENT_CASE(tcpstop);
+ NETIEVENT_CASE(tcpcancel);
+ NETIEVENT_CASE(tcpclose);
+
+ NETIEVENT_CASE(tcpdnsaccept);
+ NETIEVENT_CASE(tcpdnslisten);
+ NETIEVENT_CASE(tcpdnsconnect);
+ NETIEVENT_CASE(tcpdnssend);
+ NETIEVENT_CASE(tcpdnscancel);
+ NETIEVENT_CASE(tcpdnsclose);
+ NETIEVENT_CASE(tcpdnsread);
+ NETIEVENT_CASE(tcpdnsstop);
+
+ NETIEVENT_CASE(tlsdnscycle);
+ NETIEVENT_CASE(tlsdnsaccept);
+ NETIEVENT_CASE(tlsdnslisten);
+ NETIEVENT_CASE(tlsdnsconnect);
+ NETIEVENT_CASE(tlsdnssend);
+ NETIEVENT_CASE(tlsdnscancel);
+ NETIEVENT_CASE(tlsdnsclose);
+ NETIEVENT_CASE(tlsdnsread);
+ NETIEVENT_CASE(tlsdnsstop);
+ NETIEVENT_CASE(tlsdnsshutdown);
+
+#if HAVE_LIBNGHTTP2
+ NETIEVENT_CASE(tlsstartread);
+ NETIEVENT_CASE(tlssend);
+ NETIEVENT_CASE(tlsclose);
+ NETIEVENT_CASE(tlsdobio);
+ NETIEVENT_CASE(tlscancel);
+
+ NETIEVENT_CASE(httpsend);
+ NETIEVENT_CASE(httpclose);
+ NETIEVENT_CASE(httpendpoints);
+#endif
+ NETIEVENT_CASE(settlsctx);
+ NETIEVENT_CASE(sockstop);
+
+ NETIEVENT_CASE(connectcb);
+ NETIEVENT_CASE(readcb);
+ NETIEVENT_CASE(sendcb);
+
+ NETIEVENT_CASE(close);
+ NETIEVENT_CASE(detach);
+
+ NETIEVENT_CASE(shutdown);
+ NETIEVENT_CASE(resume);
+ NETIEVENT_CASE_NOMORE(pause);
+ default:
+ UNREACHABLE();
+ }
+ return (true);
+}
+
+static isc_result_t
+process_queue(isc__networker_t *worker, netievent_type_t type) {
+ isc__netievent_t *ievent = NULL;
+ isc__netievent_list_t list;
+
+ ISC_LIST_INIT(list);
+
+ LOCK(&worker->ievents[type].lock);
+ ISC_LIST_MOVE(list, worker->ievents[type].list);
+ UNLOCK(&worker->ievents[type].lock);
+
+ ievent = ISC_LIST_HEAD(list);
+ if (ievent == NULL) {
+ /* There's nothing scheduled */
+ return (ISC_R_EMPTY);
+ }
+
+ while (ievent != NULL) {
+ isc__netievent_t *next = ISC_LIST_NEXT(ievent, link);
+ ISC_LIST_DEQUEUE(list, ievent, link);
+
+ if (!process_netievent(worker, ievent)) {
+ /* The netievent told us to stop */
+ if (!ISC_LIST_EMPTY(list)) {
+ /*
+ * Reschedule the rest of the unprocessed
+ * events.
+ */
+ LOCK(&worker->ievents[type].lock);
+ ISC_LIST_PREPENDLIST(worker->ievents[type].list,
+ list, link);
+ UNLOCK(&worker->ievents[type].lock);
+ }
+ return (ISC_R_SUSPEND);
+ }
+
+ ievent = next;
+ }
+
+ /* We processed at least one */
+ return (ISC_R_SUCCESS);
+}
+
+void *
+isc__nm_get_netievent(isc_nm_t *mgr, isc__netievent_type type) {
+ isc__netievent_storage_t *event = isc_mem_get(mgr->mctx,
+ sizeof(*event));
+
+ *event = (isc__netievent_storage_t){ .ni.type = type };
+ ISC_LINK_INIT(&(event->ni), link);
+ return (event);
+}
+
+void
+isc__nm_put_netievent(isc_nm_t *mgr, void *ievent) {
+ isc_mem_put(mgr->mctx, ievent, sizeof(isc__netievent_storage_t));
+}
+
+NETIEVENT_SOCKET_DEF(tcpclose);
+NETIEVENT_SOCKET_DEF(tcplisten);
+NETIEVENT_SOCKET_DEF(tcppauseread);
+NETIEVENT_SOCKET_DEF(tcpstartread);
+NETIEVENT_SOCKET_DEF(tcpstop);
+NETIEVENT_SOCKET_DEF(tlsclose);
+NETIEVENT_SOCKET_DEF(tlsconnect);
+NETIEVENT_SOCKET_DEF(tlsdobio);
+NETIEVENT_SOCKET_DEF(tlsstartread);
+NETIEVENT_SOCKET_HANDLE_DEF(tlscancel);
+NETIEVENT_SOCKET_DEF(udpclose);
+NETIEVENT_SOCKET_DEF(udplisten);
+NETIEVENT_SOCKET_DEF(udpread);
+NETIEVENT_SOCKET_DEF(udpsend);
+NETIEVENT_SOCKET_DEF(udpstop);
+
+NETIEVENT_SOCKET_DEF(tcpdnsclose);
+NETIEVENT_SOCKET_DEF(tcpdnsread);
+NETIEVENT_SOCKET_DEF(tcpdnsstop);
+NETIEVENT_SOCKET_DEF(tcpdnslisten);
+NETIEVENT_SOCKET_REQ_DEF(tcpdnsconnect);
+NETIEVENT_SOCKET_REQ_DEF(tcpdnssend);
+NETIEVENT_SOCKET_HANDLE_DEF(tcpdnscancel);
+NETIEVENT_SOCKET_QUOTA_DEF(tcpdnsaccept);
+
+NETIEVENT_SOCKET_DEF(tlsdnsclose);
+NETIEVENT_SOCKET_DEF(tlsdnsread);
+NETIEVENT_SOCKET_DEF(tlsdnsstop);
+NETIEVENT_SOCKET_DEF(tlsdnslisten);
+NETIEVENT_SOCKET_REQ_DEF(tlsdnsconnect);
+NETIEVENT_SOCKET_REQ_DEF(tlsdnssend);
+NETIEVENT_SOCKET_HANDLE_DEF(tlsdnscancel);
+NETIEVENT_SOCKET_QUOTA_DEF(tlsdnsaccept);
+NETIEVENT_SOCKET_DEF(tlsdnscycle);
+NETIEVENT_SOCKET_DEF(tlsdnsshutdown);
+
+#ifdef HAVE_LIBNGHTTP2
+NETIEVENT_SOCKET_REQ_DEF(httpsend);
+NETIEVENT_SOCKET_DEF(httpclose);
+NETIEVENT_SOCKET_HTTP_EPS_DEF(httpendpoints);
+#endif /* HAVE_LIBNGHTTP2 */
+
+NETIEVENT_SOCKET_REQ_DEF(tcpconnect);
+NETIEVENT_SOCKET_REQ_DEF(tcpsend);
+NETIEVENT_SOCKET_REQ_DEF(tlssend);
+NETIEVENT_SOCKET_REQ_DEF(udpconnect);
+NETIEVENT_SOCKET_REQ_DEF(routeconnect);
+NETIEVENT_SOCKET_REQ_RESULT_DEF(connectcb);
+NETIEVENT_SOCKET_REQ_RESULT_DEF(readcb);
+NETIEVENT_SOCKET_REQ_RESULT_DEF(sendcb);
+
+NETIEVENT_SOCKET_DEF(detach);
+NETIEVENT_SOCKET_HANDLE_DEF(tcpcancel);
+NETIEVENT_SOCKET_HANDLE_DEF(udpcancel);
+
+NETIEVENT_SOCKET_QUOTA_DEF(tcpaccept);
+
+NETIEVENT_SOCKET_DEF(close);
+NETIEVENT_DEF(pause);
+NETIEVENT_DEF(resume);
+NETIEVENT_DEF(shutdown);
+NETIEVENT_DEF(stop);
+
+NETIEVENT_TASK_DEF(task);
+NETIEVENT_TASK_DEF(privilegedtask);
+
+NETIEVENT_SOCKET_TLSCTX_DEF(settlsctx);
+NETIEVENT_SOCKET_DEF(sockstop);
+
+void
+isc__nm_maybe_enqueue_ievent(isc__networker_t *worker,
+ isc__netievent_t *event) {
+ /*
+ * If we are already in the matching nmthread, process the ievent
+ * directly.
+ */
+ if (worker->id == isc_nm_tid()) {
+ process_netievent(worker, event);
+ return;
+ }
+
+ isc__nm_enqueue_ievent(worker, event);
+}
+
+void
+isc__nm_enqueue_ievent(isc__networker_t *worker, isc__netievent_t *event) {
+ netievent_type_t type;
+
+ if (event->type > netievent_prio) {
+ type = NETIEVENT_PRIORITY;
+ } else {
+ switch (event->type) {
+ case netievent_prio:
+ UNREACHABLE();
+ break;
+ case netievent_privilegedtask:
+ type = NETIEVENT_PRIVILEGED;
+ break;
+ case netievent_task:
+ type = NETIEVENT_TASK;
+ break;
+ default:
+ type = NETIEVENT_NORMAL;
+ break;
+ }
+ }
+
+ /*
+ * We need to make sure this signal will be delivered and
+ * the queue will be processed.
+ */
+ LOCK(&worker->ievents[type].lock);
+ ISC_LIST_ENQUEUE(worker->ievents[type].list, event, link);
+ if (type == NETIEVENT_PRIORITY) {
+ SIGNAL(&worker->ievents[type].cond);
+ }
+ UNLOCK(&worker->ievents[type].lock);
+
+ uv_async_send(&worker->async);
+}
+
+bool
+isc__nmsocket_active(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ if (sock->parent != NULL) {
+ return (atomic_load(&sock->parent->active));
+ }
+
+ return (atomic_load(&sock->active));
+}
+
+bool
+isc__nmsocket_deactivate(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (sock->parent != NULL) {
+ return (atomic_compare_exchange_strong(&sock->parent->active,
+ &(bool){ true }, false));
+ }
+
+ return (atomic_compare_exchange_strong(&sock->active, &(bool){ true },
+ false));
+}
+
+void
+isc___nmsocket_attach(isc_nmsocket_t *sock, isc_nmsocket_t **target FLARG) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_nmsocket_t *rsock = NULL;
+
+ if (sock->parent != NULL) {
+ rsock = sock->parent;
+ INSIST(rsock->parent == NULL); /* sanity check */
+ } else {
+ rsock = sock;
+ }
+
+ NETMGR_TRACE_LOG("isc__nmsocket_attach():%p->references = %" PRIuFAST32
+ "\n",
+ rsock, isc_refcount_current(&rsock->references) + 1);
+
+ isc_refcount_increment0(&rsock->references);
+
+ *target = sock;
+}
+
+/*
+ * Free all resources inside a socket (including its children if any).
+ */
+static void
+nmsocket_cleanup(isc_nmsocket_t *sock, bool dofree FLARG) {
+ isc_nmhandle_t *handle = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ NETMGR_TRACE_LOG("nmsocket_cleanup():%p->references = %" PRIuFAST32
+ "\n",
+ sock, isc_refcount_current(&sock->references));
+
+ isc__nm_decstats(sock, STATID_ACTIVE);
+
+ atomic_store(&sock->destroying, true);
+
+ if (sock->parent == NULL && sock->children != NULL) {
+ /*
+ * We shouldn't be here unless there are no active handles,
+ * so we can clean up and free the children.
+ */
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ if (!atomic_load(&sock->children[i].destroying)) {
+ nmsocket_cleanup(&sock->children[i],
+ false FLARG_PASS);
+ }
+ }
+
+ /*
+ * This was a parent socket: destroy the listening
+ * barriers that synchronized the children.
+ */
+ isc_barrier_destroy(&sock->startlistening);
+ isc_barrier_destroy(&sock->stoplistening);
+
+ /*
+ * Now free them.
+ */
+ isc_mem_put(sock->mgr->mctx, sock->children,
+ sock->nchildren * sizeof(*sock));
+ sock->children = NULL;
+ sock->nchildren = 0;
+ }
+
+ sock->statichandle = NULL;
+
+ if (sock->outerhandle != NULL) {
+ isc__nmhandle_detach(&sock->outerhandle FLARG_PASS);
+ }
+
+ if (sock->outer != NULL) {
+ isc___nmsocket_detach(&sock->outer FLARG_PASS);
+ }
+
+ while ((handle = isc_astack_pop(sock->inactivehandles)) != NULL) {
+ nmhandle_free(sock, handle);
+ }
+
+ if (sock->buf != NULL) {
+ isc_mem_put(sock->mgr->mctx, sock->buf, sock->buf_size);
+ }
+
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ sock->pquota = NULL;
+
+ isc_astack_destroy(sock->inactivehandles);
+
+ while ((uvreq = isc_astack_pop(sock->inactivereqs)) != NULL) {
+ isc_mem_put(sock->mgr->mctx, uvreq, sizeof(*uvreq));
+ }
+
+ isc_astack_destroy(sock->inactivereqs);
+ sock->magic = 0;
+
+ isc_condition_destroy(&sock->scond);
+ isc_condition_destroy(&sock->cond);
+ isc_mutex_destroy(&sock->lock);
+ isc__nm_tlsdns_cleanup_data(sock);
+#if HAVE_LIBNGHTTP2
+ isc__nm_tls_cleanup_data(sock);
+ isc__nm_http_cleanup_data(sock);
+#endif
+
+ INSIST(ISC_LIST_EMPTY(sock->tls.sendreqs));
+
+ if (sock->barrier_initialised) {
+ isc_barrier_destroy(&sock->barrier);
+ }
+
+#ifdef NETMGR_TRACE
+ LOCK(&sock->mgr->lock);
+ ISC_LIST_UNLINK(sock->mgr->active_sockets, sock, active_link);
+ UNLOCK(&sock->mgr->lock);
+#endif
+ if (dofree) {
+ isc_nm_t *mgr = sock->mgr;
+ isc_mem_put(mgr->mctx, sock, sizeof(*sock));
+ isc_nm_detach(&mgr);
+ } else {
+ isc_nm_detach(&sock->mgr);
+ }
+}
+
+static void
+nmsocket_maybe_destroy(isc_nmsocket_t *sock FLARG) {
+ int active_handles;
+ bool destroy = false;
+
+ NETMGR_TRACE_LOG("%s():%p->references = %" PRIuFAST32 "\n", __func__,
+ sock, isc_refcount_current(&sock->references));
+
+ if (sock->parent != NULL) {
+ /*
+ * This is a child socket and cannot be destroyed except
+ * as a side effect of destroying the parent, so let's go
+ * see if the parent is ready to be destroyed.
+ */
+ nmsocket_maybe_destroy(sock->parent FLARG_PASS);
+ return;
+ }
+
+ /*
+ * This is a parent socket (or a standalone). See whether the
+ * children have active handles before deciding whether to
+ * accept destruction.
+ */
+ LOCK(&sock->lock);
+ if (atomic_load(&sock->active) || atomic_load(&sock->destroying) ||
+ !atomic_load(&sock->closed) || atomic_load(&sock->references) != 0)
+ {
+ UNLOCK(&sock->lock);
+ return;
+ }
+
+ active_handles = atomic_load(&sock->ah);
+ if (sock->children != NULL) {
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ LOCK(&sock->children[i].lock);
+ active_handles += atomic_load(&sock->children[i].ah);
+ UNLOCK(&sock->children[i].lock);
+ }
+ }
+
+ if (active_handles == 0 || sock->statichandle != NULL) {
+ destroy = true;
+ }
+
+ NETMGR_TRACE_LOG("%s:%p->active_handles = %d, .statichandle = %p\n",
+ __func__, sock, active_handles, sock->statichandle);
+
+ if (destroy) {
+ atomic_store(&sock->destroying, true);
+ UNLOCK(&sock->lock);
+ nmsocket_cleanup(sock, true FLARG_PASS);
+ } else {
+ UNLOCK(&sock->lock);
+ }
+}
+
+void
+isc___nmsocket_prep_destroy(isc_nmsocket_t *sock FLARG) {
+ REQUIRE(sock->parent == NULL);
+
+ NETMGR_TRACE_LOG("isc___nmsocket_prep_destroy():%p->references = "
+ "%" PRIuFAST32 "\n",
+ sock, isc_refcount_current(&sock->references));
+
+ /*
+ * The final external reference to the socket is gone. We can try
+ * destroying the socket, but we have to wait for all the inflight
+ * handles to finish first.
+ */
+ atomic_store(&sock->active, false);
+
+ /*
+ * If the socket has children, they'll need to be marked inactive
+ * so they can be cleaned up too.
+ */
+ if (sock->children != NULL) {
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ atomic_store(&sock->children[i].active, false);
+ }
+ }
+
+ /*
+ * If we're here then we already stopped listening; otherwise
+ * we'd have a hanging reference from the listening process.
+ *
+ * If it's a regular socket we may need to close it.
+ */
+ if (!atomic_load(&sock->closed)) {
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ isc__nm_udp_close(sock);
+ return;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_close(sock);
+ return;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_close(sock);
+ return;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_close(sock);
+ return;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_close(sock);
+ break;
+ case isc_nm_httpsocket:
+ isc__nm_http_close(sock);
+ return;
+#endif
+ default:
+ break;
+ }
+ }
+
+ nmsocket_maybe_destroy(sock FLARG_PASS);
+}
+
+void
+isc___nmsocket_detach(isc_nmsocket_t **sockp FLARG) {
+ REQUIRE(sockp != NULL && *sockp != NULL);
+ REQUIRE(VALID_NMSOCK(*sockp));
+
+ isc_nmsocket_t *sock = *sockp, *rsock = NULL;
+ *sockp = NULL;
+
+ /*
+ * If the socket is a part of a set (a child socket) we are
+ * counting references for the whole set at the parent.
+ */
+ if (sock->parent != NULL) {
+ rsock = sock->parent;
+ INSIST(rsock->parent == NULL); /* Sanity check */
+ } else {
+ rsock = sock;
+ }
+
+ NETMGR_TRACE_LOG("isc__nmsocket_detach():%p->references = %" PRIuFAST32
+ "\n",
+ rsock, isc_refcount_current(&rsock->references) - 1);
+
+ if (isc_refcount_decrement(&rsock->references) == 1) {
+ isc___nmsocket_prep_destroy(rsock FLARG_PASS);
+ }
+}
+
+void
+isc_nmsocket_close(isc_nmsocket_t **sockp) {
+ REQUIRE(sockp != NULL);
+ REQUIRE(VALID_NMSOCK(*sockp));
+ REQUIRE((*sockp)->type == isc_nm_udplistener ||
+ (*sockp)->type == isc_nm_tcplistener ||
+ (*sockp)->type == isc_nm_tcpdnslistener ||
+ (*sockp)->type == isc_nm_tlsdnslistener ||
+ (*sockp)->type == isc_nm_tlslistener ||
+ (*sockp)->type == isc_nm_httplistener);
+
+ isc__nmsocket_detach(sockp);
+}
+
+void
+isc___nmsocket_init(isc_nmsocket_t *sock, isc_nm_t *mgr, isc_nmsocket_type type,
+ isc_sockaddr_t *iface FLARG) {
+ uint16_t family;
+
+ REQUIRE(sock != NULL);
+ REQUIRE(mgr != NULL);
+
+ *sock = (isc_nmsocket_t){ .type = type,
+ .fd = -1,
+ .inactivehandles = isc_astack_new(
+ mgr->mctx, ISC_NM_HANDLES_STACK_SIZE),
+ .inactivereqs = isc_astack_new(
+ mgr->mctx, ISC_NM_REQS_STACK_SIZE) };
+
+ ISC_LIST_INIT(sock->tls.sendreqs);
+
+ if (iface != NULL) {
+ family = iface->type.sa.sa_family;
+ sock->iface = *iface;
+ } else {
+ family = AF_UNSPEC;
+ }
+
+#if NETMGR_TRACE
+ sock->backtrace_size = isc_backtrace(sock->backtrace, TRACE_SIZE);
+ ISC_LINK_INIT(sock, active_link);
+ ISC_LIST_INIT(sock->active_handles);
+ LOCK(&mgr->lock);
+ ISC_LIST_APPEND(mgr->active_sockets, sock, active_link);
+ UNLOCK(&mgr->lock);
+#endif
+
+ isc_nm_attach(mgr, &sock->mgr);
+ sock->uv_handle.handle.data = sock;
+
+ ISC_LINK_INIT(&sock->quotacb, link);
+
+ switch (type) {
+ case isc_nm_udpsocket:
+ case isc_nm_udplistener:
+ switch (family) {
+ case AF_INET:
+ sock->statsindex = udp4statsindex;
+ break;
+ case AF_INET6:
+ sock->statsindex = udp6statsindex;
+ break;
+ case AF_UNSPEC:
+ /*
+ * Route sockets are AF_UNSPEC, and don't
+ * have stats counters.
+ */
+ break;
+ default:
+ UNREACHABLE();
+ }
+ break;
+ case isc_nm_tcpsocket:
+ case isc_nm_tcplistener:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tcpdnslistener:
+ case isc_nm_tlsdnssocket:
+ case isc_nm_tlsdnslistener:
+ case isc_nm_httpsocket:
+ case isc_nm_httplistener:
+ switch (family) {
+ case AF_INET:
+ sock->statsindex = tcp4statsindex;
+ break;
+ case AF_INET6:
+ sock->statsindex = tcp6statsindex;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ break;
+ default:
+ break;
+ }
+
+ isc_mutex_init(&sock->lock);
+ isc_condition_init(&sock->cond);
+ isc_condition_init(&sock->scond);
+ isc_refcount_init(&sock->references, 1);
+
+#if HAVE_LIBNGHTTP2
+ memset(&sock->tlsstream, 0, sizeof(sock->tlsstream));
+#endif /* HAVE_LIBNGHTTP2 */
+
+ NETMGR_TRACE_LOG("isc__nmsocket_init():%p->references = %" PRIuFAST32
+ "\n",
+ sock, isc_refcount_current(&sock->references));
+
+ atomic_init(&sock->active, true);
+ atomic_init(&sock->sequential, false);
+ atomic_init(&sock->readpaused, false);
+ atomic_init(&sock->closing, false);
+ atomic_init(&sock->listening, 0);
+ atomic_init(&sock->closed, 0);
+ atomic_init(&sock->destroying, 0);
+ atomic_init(&sock->ah, 0);
+ atomic_init(&sock->client, 0);
+ atomic_init(&sock->connecting, false);
+ atomic_init(&sock->keepalive, false);
+ atomic_init(&sock->connected, false);
+ atomic_init(&sock->timedout, false);
+
+ atomic_init(&sock->active_child_connections, 0);
+
+#if HAVE_LIBNGHTTP2
+ isc__nm_http_initsocket(sock);
+#endif
+
+ sock->magic = NMSOCK_MAGIC;
+
+ isc__nm_incstats(sock, STATID_ACTIVE);
+}
+
+void
+isc__nmsocket_clearcb(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(!isc__nm_in_netthread() || sock->tid == isc_nm_tid());
+
+ sock->recv_cb = NULL;
+ sock->recv_cbarg = NULL;
+ sock->accept_cb = NULL;
+ sock->accept_cbarg = NULL;
+ sock->connect_cb = NULL;
+ sock->connect_cbarg = NULL;
+}
+
+void
+isc__nm_free_uvbuf(isc_nmsocket_t *sock, const uv_buf_t *buf) {
+ isc__networker_t *worker = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ worker = &sock->mgr->workers[sock->tid];
+ REQUIRE(buf->base == worker->recvbuf);
+
+ worker->recvbuf_inuse = false;
+}
+
+static isc_nmhandle_t *
+alloc_handle(isc_nmsocket_t *sock) {
+ isc_nmhandle_t *handle =
+ isc_mem_get(sock->mgr->mctx,
+ sizeof(isc_nmhandle_t) + sock->extrahandlesize);
+
+ *handle = (isc_nmhandle_t){ .magic = NMHANDLE_MAGIC };
+#ifdef NETMGR_TRACE
+ ISC_LINK_INIT(handle, active_link);
+#endif
+ isc_refcount_init(&handle->references, 1);
+
+ return (handle);
+}
+
+isc_nmhandle_t *
+isc___nmhandle_get(isc_nmsocket_t *sock, isc_sockaddr_t *peer,
+ isc_sockaddr_t *local FLARG) {
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ handle = isc_astack_pop(sock->inactivehandles);
+
+ if (handle == NULL) {
+ handle = alloc_handle(sock);
+ } else {
+ isc_refcount_init(&handle->references, 1);
+ INSIST(VALID_NMHANDLE(handle));
+ }
+
+ NETMGR_TRACE_LOG(
+ "isc__nmhandle_get():handle %p->references = %" PRIuFAST32 "\n",
+ handle, isc_refcount_current(&handle->references));
+
+ isc___nmsocket_attach(sock, &handle->sock FLARG_PASS);
+
+#if NETMGR_TRACE
+ handle->backtrace_size = isc_backtrace(handle->backtrace, TRACE_SIZE);
+#endif
+
+ if (peer != NULL) {
+ handle->peer = *peer;
+ } else {
+ handle->peer = sock->peer;
+ }
+
+ if (local != NULL) {
+ handle->local = *local;
+ } else {
+ handle->local = sock->iface;
+ }
+
+ (void)atomic_fetch_add(&sock->ah, 1);
+
+#ifdef NETMGR_TRACE
+ LOCK(&sock->lock);
+ ISC_LIST_APPEND(sock->active_handles, handle, active_link);
+ UNLOCK(&sock->lock);
+#endif
+
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ if (!atomic_load(&sock->client)) {
+ break;
+ }
+ FALLTHROUGH;
+ case isc_nm_tcpsocket:
+ case isc_nm_tlssocket:
+ INSIST(sock->statichandle == NULL);
+
+ /*
+ * statichandle must be assigned, not attached;
+ * otherwise, if a handle was detached elsewhere
+ * it could never reach 0 references, and the
+ * handle and socket would never be freed.
+ */
+ sock->statichandle = handle;
+ break;
+ default:
+ break;
+ }
+
+#if HAVE_LIBNGHTTP2
+ if (sock->type == isc_nm_httpsocket && sock->h2.session) {
+ isc__nm_httpsession_attach(sock->h2.session,
+ &handle->httpsession);
+ }
+#endif
+
+ return (handle);
+}
+
+void
+isc__nmhandle_attach(isc_nmhandle_t *handle, isc_nmhandle_t **handlep FLARG) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(handlep != NULL && *handlep == NULL);
+
+ NETMGR_TRACE_LOG("isc__nmhandle_attach():handle %p->references = "
+ "%" PRIuFAST32 "\n",
+ handle, isc_refcount_current(&handle->references) + 1);
+
+ isc_refcount_increment(&handle->references);
+ *handlep = handle;
+}
+
+bool
+isc_nmhandle_is_stream(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ return (handle->sock->type == isc_nm_tcpsocket ||
+ handle->sock->type == isc_nm_tcpdnssocket ||
+ handle->sock->type == isc_nm_tlssocket ||
+ handle->sock->type == isc_nm_tlsdnssocket ||
+ handle->sock->type == isc_nm_httpsocket);
+}
+
+static void
+nmhandle_free(isc_nmsocket_t *sock, isc_nmhandle_t *handle) {
+ size_t extra = sock->extrahandlesize;
+
+ isc_refcount_destroy(&handle->references);
+
+ if (handle->dofree != NULL) {
+ handle->dofree(handle->opaque);
+ }
+
+ *handle = (isc_nmhandle_t){ .magic = 0 };
+
+ isc_mem_put(sock->mgr->mctx, handle, sizeof(isc_nmhandle_t) + extra);
+}
+
+static void
+nmhandle_deactivate(isc_nmsocket_t *sock, isc_nmhandle_t *handle) {
+ bool reuse = false;
+ uint_fast32_t ah;
+
+ /*
+ * We do all of this under lock to avoid races with socket
+ * destruction. We have to do this now, because at this point the
+ * socket is either unused or still attached to event->sock.
+ */
+ LOCK(&sock->lock);
+
+#ifdef NETMGR_TRACE
+ ISC_LIST_UNLINK(sock->active_handles, handle, active_link);
+#endif
+
+ ah = atomic_fetch_sub(&sock->ah, 1);
+ INSIST(ah > 0);
+
+#if !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__
+ if (atomic_load(&sock->active)) {
+ reuse = isc_astack_trypush(sock->inactivehandles, handle);
+ }
+#endif /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */
+ if (!reuse) {
+ nmhandle_free(sock, handle);
+ }
+ UNLOCK(&sock->lock);
+}
+
+void
+isc__nmhandle_detach(isc_nmhandle_t **handlep FLARG) {
+ isc_nmsocket_t *sock = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(handlep != NULL);
+ REQUIRE(VALID_NMHANDLE(*handlep));
+
+ handle = *handlep;
+ *handlep = NULL;
+
+ /*
+ * If the closehandle_cb is set, it needs to run asynchronously to
+ * ensure correct ordering of the isc__nm_process_sock_buffer().
+ */
+ sock = handle->sock;
+ if (sock->tid == isc_nm_tid() && sock->closehandle_cb == NULL) {
+ nmhandle_detach_cb(&handle FLARG_PASS);
+ } else {
+ isc__netievent_detach_t *event =
+ isc__nm_get_netievent_detach(sock->mgr, sock);
+ /*
+ * we are using implicit "attach" as the last reference
+ * need to be destroyed explicitly in the async callback
+ */
+ event->handle = handle;
+ FLARG_IEVENT_PASS(event);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ }
+}
+
+static void
+nmhandle_detach_cb(isc_nmhandle_t **handlep FLARG) {
+ isc_nmsocket_t *sock = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(handlep != NULL);
+ REQUIRE(VALID_NMHANDLE(*handlep));
+
+ handle = *handlep;
+ *handlep = NULL;
+
+ NETMGR_TRACE_LOG("isc__nmhandle_detach():%p->references = %" PRIuFAST32
+ "\n",
+ handle, isc_refcount_current(&handle->references) - 1);
+
+ if (isc_refcount_decrement(&handle->references) > 1) {
+ return;
+ }
+
+ /* We need an acquire memory barrier here */
+ (void)isc_refcount_current(&handle->references);
+
+ sock = handle->sock;
+ handle->sock = NULL;
+
+ if (handle->doreset != NULL) {
+ handle->doreset(handle->opaque);
+ }
+
+#if HAVE_LIBNGHTTP2
+ if (sock->type == isc_nm_httpsocket && handle->httpsession != NULL) {
+ isc__nm_httpsession_detach(&handle->httpsession);
+ }
+#endif
+
+ nmhandle_deactivate(sock, handle);
+
+ /*
+ * The handle is gone now. If the socket has a callback configured
+ * for that (e.g., to perform cleanup after request processing),
+ * call it now, or schedule it to run asynchronously.
+ */
+ if (sock->closehandle_cb != NULL) {
+ if (sock->tid == isc_nm_tid()) {
+ sock->closehandle_cb(sock);
+ } else {
+ isc__netievent_close_t *event =
+ isc__nm_get_netievent_close(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ }
+ }
+
+ if (handle == sock->statichandle) {
+ /* statichandle is assigned, not attached. */
+ sock->statichandle = NULL;
+ }
+
+ isc___nmsocket_detach(&sock FLARG_PASS);
+}
+
+void *
+isc_nmhandle_getdata(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ return (handle->opaque);
+}
+
+void
+isc_nmhandle_setdata(isc_nmhandle_t *handle, void *arg,
+ isc_nm_opaquecb_t doreset, isc_nm_opaquecb_t dofree) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ handle->opaque = arg;
+ handle->doreset = doreset;
+ handle->dofree = dofree;
+}
+
+void
+isc__nm_alloc_dnsbuf(isc_nmsocket_t *sock, size_t len) {
+ REQUIRE(len <= NM_BIG_BUF);
+
+ if (sock->buf == NULL) {
+ /* We don't have the buffer at all */
+ size_t alloc_len = len < NM_REG_BUF ? NM_REG_BUF : NM_BIG_BUF;
+ sock->buf = isc_mem_get(sock->mgr->mctx, alloc_len);
+ sock->buf_size = alloc_len;
+ } else {
+ /* We have the buffer but it's too small */
+ sock->buf = isc_mem_reget(sock->mgr->mctx, sock->buf,
+ sock->buf_size, NM_BIG_BUF);
+ sock->buf_size = NM_BIG_BUF;
+ }
+}
+
+void
+isc__nm_failed_send_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_result_t eresult) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+
+ if (req->cb.send != NULL) {
+ isc__nm_sendcb(sock, req, eresult, true);
+ } else {
+ isc__nm_uvreq_put(&req, sock);
+ }
+}
+
+void
+isc__nm_failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
+ REQUIRE(atomic_load(&sock->accepting));
+ REQUIRE(sock->server);
+
+ /*
+ * Detach the quota early to make room for other connections;
+ * otherwise it'd be detached later asynchronously, and clog
+ * the quota unnecessarily.
+ */
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ isc__nmsocket_detach(&sock->server);
+
+ atomic_store(&sock->accepting, false);
+
+ switch (eresult) {
+ case ISC_R_NOTCONNECTED:
+ /* IGNORE: The client disconnected before we could accept */
+ break;
+ default:
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+ "Accepting TCP connection failed: %s",
+ isc_result_totext(eresult));
+ }
+}
+
+void
+isc__nm_failed_connect_cb(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_result_t eresult, bool async) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(req->cb.connect != NULL);
+
+ isc__nm_incstats(sock, STATID_CONNECTFAIL);
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ atomic_compare_exchange_enforced(&sock->connecting, &(bool){ true },
+ false);
+
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, eresult, async);
+
+ isc__nmsocket_prep_destroy(sock);
+}
+
+void
+isc__nm_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, bool async) {
+ REQUIRE(VALID_NMSOCK(sock));
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ isc__nm_udp_failed_read_cb(sock, result);
+ return;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_failed_read_cb(sock, result);
+ return;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_failed_read_cb(sock, result);
+ return;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_failed_read_cb(sock, result, async);
+ return;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc__nmsocket_connecttimeout_cb(uv_timer_t *timer) {
+ uv_connect_t *uvreq = uv_handle_get_data((uv_handle_t *)timer);
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle);
+ isc__nm_uvreq_t *req = uv_handle_get_data((uv_handle_t *)uvreq);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->connecting));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_NMHANDLE(req->handle));
+
+ isc__nmsocket_timer_stop(sock);
+
+ /*
+ * Mark the connection as timed out and shutdown the socket.
+ */
+ atomic_compare_exchange_enforced(&sock->timedout, &(bool){ false },
+ true);
+ isc__nmsocket_clearcb(sock);
+ isc__nmsocket_shutdown(sock);
+}
+
+void
+isc__nm_accept_connection_log(isc_result_t result, bool can_log_quota) {
+ int level;
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case ISC_R_NOCONN:
+ return;
+ case ISC_R_QUOTA:
+ case ISC_R_SOFTQUOTA:
+ if (!can_log_quota) {
+ return;
+ }
+ level = ISC_LOG_INFO;
+ break;
+ case ISC_R_NOTCONNECTED:
+ level = ISC_LOG_INFO;
+ break;
+ default:
+ level = ISC_LOG_ERROR;
+ }
+
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ level, "Accepting TCP connection failed: %s",
+ isc_result_totext(result));
+}
+
+void
+isc__nmsocket_writetimeout_cb(void *data, isc_result_t eresult) {
+ isc__nm_uvreq_t *req = data;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(eresult == ISC_R_TIMEDOUT);
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_NMSOCK(req->sock));
+
+ sock = req->sock;
+
+ isc__nmsocket_reset(sock);
+}
+
+void
+isc__nmsocket_readtimeout_cb(uv_timer_t *timer) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)timer);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->reading));
+
+ if (atomic_load(&sock->client)) {
+ uv_timer_stop(timer);
+
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nm_readcb(sock, req, ISC_R_TIMEDOUT);
+ }
+
+ if (!isc__nmsocket_timer_running(sock)) {
+ isc__nmsocket_clearcb(sock);
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ }
+ } else {
+ isc__nm_failed_read_cb(sock, ISC_R_TIMEDOUT, false);
+ }
+}
+
+void
+isc__nmsocket_timer_restart(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (uv_is_closing((uv_handle_t *)&sock->read_timer)) {
+ return;
+ }
+
+ if (atomic_load(&sock->connecting)) {
+ int r;
+
+ if (sock->connect_timeout == 0) {
+ return;
+ }
+
+ r = uv_timer_start(&sock->read_timer,
+ isc__nmsocket_connecttimeout_cb,
+ sock->connect_timeout + 10, 0);
+ UV_RUNTIME_CHECK(uv_timer_start, r);
+
+ } else {
+ int r;
+
+ if (sock->read_timeout == 0) {
+ return;
+ }
+
+ r = uv_timer_start(&sock->read_timer,
+ isc__nmsocket_readtimeout_cb,
+ sock->read_timeout, 0);
+ UV_RUNTIME_CHECK(uv_timer_start, r);
+ }
+}
+
+bool
+isc__nmsocket_timer_running(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ return (uv_is_active((uv_handle_t *)&sock->read_timer));
+}
+
+void
+isc__nmsocket_timer_start(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (isc__nmsocket_timer_running(sock)) {
+ return;
+ }
+
+ isc__nmsocket_timer_restart(sock);
+}
+
+void
+isc__nmsocket_timer_stop(isc_nmsocket_t *sock) {
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ /* uv_timer_stop() is idempotent, no need to check if running */
+
+ r = uv_timer_stop(&sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_stop, r);
+}
+
+isc__nm_uvreq_t *
+isc__nm_get_read_req(isc_nmsocket_t *sock, isc_sockaddr_t *sockaddr) {
+ isc__nm_uvreq_t *req = NULL;
+
+ req = isc__nm_uvreq_get(sock->mgr, sock);
+ req->cb.recv = sock->recv_cb;
+ req->cbarg = sock->recv_cbarg;
+
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ case isc_nm_tlssocket:
+ isc_nmhandle_attach(sock->statichandle, &req->handle);
+ break;
+ default:
+ if (atomic_load(&sock->client) && sock->statichandle != NULL) {
+ isc_nmhandle_attach(sock->statichandle, &req->handle);
+ } else {
+ req->handle = isc__nmhandle_get(sock, sockaddr, NULL);
+ }
+ break;
+ }
+
+ return (req);
+}
+
+/*%<
+ * Allocator callback for read operations.
+ *
+ * Note this doesn't actually allocate anything, it just assigns the
+ * worker's receive buffer to a socket, and marks it as "in use".
+ */
+void
+isc__nm_alloc_cb(uv_handle_t *handle, size_t size, uv_buf_t *buf) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ isc__networker_t *worker = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(isc__nm_in_netthread());
+ /*
+ * The size provided by libuv is only suggested size, and it always
+ * defaults to 64 * 1024 in the current versions of libuv (see
+ * src/unix/udp.c and src/unix/stream.c).
+ */
+ UNUSED(size);
+
+ worker = &sock->mgr->workers[sock->tid];
+ INSIST(!worker->recvbuf_inuse);
+ INSIST(worker->recvbuf != NULL);
+
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ buf->len = ISC_NETMGR_UDP_RECVBUF_SIZE;
+ break;
+ case isc_nm_tcpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ buf->len = ISC_NETMGR_TCP_RECVBUF_SIZE;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ REQUIRE(buf->len <= ISC_NETMGR_RECVBUF_SIZE);
+ buf->base = worker->recvbuf;
+
+ worker->recvbuf_inuse = true;
+}
+
+isc_result_t
+isc__nm_start_reading(isc_nmsocket_t *sock) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int r;
+
+ if (atomic_load(&sock->reading)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
+ isc__nm_udp_read_cb);
+ break;
+ case isc_nm_tcpsocket:
+ r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
+ isc__nm_tcp_read_cb);
+ break;
+ case isc_nm_tcpdnssocket:
+ r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
+ isc__nm_tcpdns_read_cb);
+ break;
+ case isc_nm_tlsdnssocket:
+ r = uv_read_start(&sock->uv_handle.stream, isc__nm_alloc_cb,
+ isc__nm_tlsdns_read_cb);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ } else {
+ atomic_store(&sock->reading, true);
+ }
+
+ return (result);
+}
+
+void
+isc__nm_stop_reading(isc_nmsocket_t *sock) {
+ int r;
+
+ if (!atomic_load(&sock->reading)) {
+ return;
+ }
+
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ r = uv_udp_recv_stop(&sock->uv_handle.udp);
+ UV_RUNTIME_CHECK(uv_udp_recv_stop, r);
+ break;
+ case isc_nm_tcpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ r = uv_read_stop(&sock->uv_handle.stream);
+ UV_RUNTIME_CHECK(uv_read_stop, r);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ atomic_store(&sock->reading, false);
+}
+
+bool
+isc__nm_closing(isc_nmsocket_t *sock) {
+ return (atomic_load(&sock->mgr->closing));
+}
+
+bool
+isc__nmsocket_closing(isc_nmsocket_t *sock) {
+ return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
+ isc__nm_closing(sock) ||
+ (sock->server != NULL && !isc__nmsocket_active(sock->server)));
+}
+
+static isc_result_t
+processbuffer(isc_nmsocket_t *sock) {
+ switch (sock->type) {
+ case isc_nm_tcpdnssocket:
+ return (isc__nm_tcpdns_processbuffer(sock));
+ case isc_nm_tlsdnssocket:
+ return (isc__nm_tlsdns_processbuffer(sock));
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * Process a DNS message.
+ *
+ * If we only have an incomplete DNS message, we don't touch any
+ * timers. If we do have a full message, reset the timer.
+ *
+ * Stop reading if this is a client socket, or if the server socket
+ * has been set to sequential mode. In this case we'll be called again
+ * later by isc__nm_resume_processing().
+ */
+isc_result_t
+isc__nm_process_sock_buffer(isc_nmsocket_t *sock) {
+ for (;;) {
+ int_fast32_t ah = atomic_load(&sock->ah);
+ isc_result_t result = processbuffer(sock);
+ switch (result) {
+ case ISC_R_NOMORE:
+ /*
+ * Don't reset the timer until we have a
+ * full DNS message.
+ */
+ result = isc__nm_start_reading(sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ /*
+ * Start the timer only if there are no externally used
+ * active handles, there's always one active handle
+ * attached internally to sock->recv_handle in
+ * accept_connection()
+ */
+ if (ah == 1) {
+ isc__nmsocket_timer_start(sock);
+ }
+ goto done;
+ case ISC_R_CANCELED:
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+ goto done;
+ case ISC_R_SUCCESS:
+ /*
+ * Stop the timer on the successful message read, this
+ * also allows to restart the timer when we have no more
+ * data.
+ */
+ isc__nmsocket_timer_stop(sock);
+
+ if (atomic_load(&sock->client) ||
+ atomic_load(&sock->sequential))
+ {
+ isc__nm_stop_reading(sock);
+ goto done;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+done:
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_resume_processing(void *arg) {
+ isc_nmsocket_t *sock = (isc_nmsocket_t *)arg;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(!atomic_load(&sock->client));
+
+ if (isc__nmsocket_closing(sock)) {
+ return;
+ }
+
+ isc__nm_process_sock_buffer(sock);
+}
+
+void
+isc_nmhandle_cleartimeout(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ switch (handle->sock->type) {
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httpsocket:
+ isc__nm_http_cleartimeout(handle);
+ return;
+ case isc_nm_tlssocket:
+ isc__nm_tls_cleartimeout(handle);
+ return;
+#endif
+ default:
+ handle->sock->read_timeout = 0;
+
+ if (uv_is_active((uv_handle_t *)&handle->sock->read_timer)) {
+ isc__nmsocket_timer_stop(handle->sock);
+ }
+ }
+}
+
+void
+isc_nmhandle_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ switch (handle->sock->type) {
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httpsocket:
+ isc__nm_http_settimeout(handle, timeout);
+ return;
+ case isc_nm_tlssocket:
+ isc__nm_tls_settimeout(handle, timeout);
+ return;
+#endif
+ default:
+ handle->sock->read_timeout = timeout;
+ isc__nmsocket_timer_restart(handle->sock);
+ }
+}
+
+void
+isc_nmhandle_keepalive(isc_nmhandle_t *handle, bool value) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ atomic_store(&sock->keepalive, value);
+ sock->read_timeout = value ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle);
+ sock->write_timeout = value ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nmhandle_tls_keepalive(handle, value);
+ break;
+ case isc_nm_httpsocket:
+ isc__nmhandle_http_keepalive(handle, value);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ /*
+ * For any other protocol, this is a no-op.
+ */
+ return;
+ }
+}
+
+bool
+isc_nmhandle_timer_running(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ return (isc__nmsocket_timer_running(handle->sock));
+}
+
+void *
+isc_nmhandle_getextra(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ return (handle->extra);
+}
+
+isc_sockaddr_t
+isc_nmhandle_peeraddr(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ return (handle->peer);
+}
+
+isc_sockaddr_t
+isc_nmhandle_localaddr(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ return (handle->local);
+}
+
+isc_nm_t *
+isc_nmhandle_netmgr(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ return (handle->sock->mgr);
+}
+
+isc__nm_uvreq_t *
+isc___nm_uvreq_get(isc_nm_t *mgr, isc_nmsocket_t *sock FLARG) {
+ isc__nm_uvreq_t *req = NULL;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (sock != NULL && isc__nmsocket_active(sock)) {
+ /* Try to reuse one */
+ req = isc_astack_pop(sock->inactivereqs);
+ }
+
+ if (req == NULL) {
+ req = isc_mem_get(mgr->mctx, sizeof(*req));
+ }
+
+ *req = (isc__nm_uvreq_t){
+ .magic = 0,
+ .connect_tries = 3,
+ };
+ ISC_LINK_INIT(req, link);
+ req->uv_req.req.data = req;
+ isc___nmsocket_attach(sock, &req->sock FLARG_PASS);
+ req->magic = UVREQ_MAGIC;
+
+ return (req);
+}
+
+void
+isc___nm_uvreq_put(isc__nm_uvreq_t **req0, isc_nmsocket_t *sock FLARG) {
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(req0 != NULL);
+ REQUIRE(VALID_UVREQ(*req0));
+
+ req = *req0;
+ *req0 = NULL;
+
+ INSIST(sock == req->sock);
+
+ req->magic = 0;
+
+ /*
+ * We need to save this first to make sure that handle,
+ * sock, and the netmgr won't all disappear.
+ */
+ handle = req->handle;
+ req->handle = NULL;
+
+#if !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__
+ if (!isc__nmsocket_active(sock) ||
+ !isc_astack_trypush(sock->inactivereqs, req))
+ {
+ isc_mem_put(sock->mgr->mctx, req, sizeof(*req));
+ }
+#else /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */
+ isc_mem_put(sock->mgr->mctx, req, sizeof(*req));
+#endif /* !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ */
+
+ if (handle != NULL) {
+ isc__nmhandle_detach(&handle FLARG_PASS);
+ }
+
+ isc___nmsocket_detach(&sock FLARG_PASS);
+}
+
+void
+isc_nm_send(isc_nmhandle_t *handle, isc_region_t *region, isc_nm_cb_t cb,
+ void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ switch (handle->sock->type) {
+ case isc_nm_udpsocket:
+ case isc_nm_udplistener:
+ isc__nm_udp_send(handle, region, cb, cbarg);
+ break;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_send(handle, region, cb, cbarg);
+ break;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_send(handle, region, cb, cbarg);
+ break;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_send(handle, region, cb, cbarg);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_send(handle, region, cb, cbarg);
+ break;
+ case isc_nm_httpsocket:
+ isc__nm_http_send(handle, region, cb, cbarg);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ switch (handle->sock->type) {
+ case isc_nm_udpsocket:
+ isc__nm_udp_read(handle, cb, cbarg);
+ break;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_read(handle, cb, cbarg);
+ break;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_read(handle, cb, cbarg);
+ break;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_read(handle, cb, cbarg);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_read(handle, cb, cbarg);
+ break;
+ case isc_nm_httpsocket:
+ isc__nm_http_read(handle, cb, cbarg);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_cancelread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ switch (handle->sock->type) {
+ case isc_nm_udpsocket:
+ isc__nm_udp_cancelread(handle);
+ break;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_cancelread(handle);
+ break;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_cancelread(handle);
+ break;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_cancelread(handle);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_cancelread(handle);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_pauseread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ isc_nmsocket_t *sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_pauseread(handle);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_pauseread(handle);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_resumeread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ isc_nmsocket_t *sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_resumeread(handle);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ isc__nm_tls_resumeread(handle);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_nm_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ switch (sock->type) {
+ case isc_nm_udplistener:
+ isc__nm_udp_stoplistening(sock);
+ break;
+ case isc_nm_tcpdnslistener:
+ isc__nm_tcpdns_stoplistening(sock);
+ break;
+ case isc_nm_tcplistener:
+ isc__nm_tcp_stoplistening(sock);
+ break;
+ case isc_nm_tlsdnslistener:
+ isc__nm_tlsdns_stoplistening(sock);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlslistener:
+ isc__nm_tls_stoplistening(sock);
+ break;
+ case isc_nm_httplistener:
+ isc__nm_http_stoplistening(sock);
+ break;
+#endif
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc__nmsocket_stop(isc_nmsocket_t *listener) {
+ isc__netievent_sockstop_t ievent = { .sock = listener };
+
+ REQUIRE(VALID_NMSOCK(listener));
+
+ if (!atomic_compare_exchange_strong(&listener->closing,
+ &(bool){ false }, true))
+ {
+ UNREACHABLE();
+ }
+
+ for (size_t i = 0; i < listener->nchildren; i++) {
+ isc__networker_t *worker = &listener->mgr->workers[i];
+ isc__netievent_sockstop_t *ev;
+
+ if (isc__nm_in_netthread() && i == (size_t)isc_nm_tid()) {
+ continue;
+ }
+
+ ev = isc__nm_get_netievent_sockstop(listener->mgr, listener);
+ isc__nm_enqueue_ievent(worker, (isc__netievent_t *)ev);
+ }
+
+ if (isc__nm_in_netthread()) {
+ isc__nm_async_sockstop(&listener->mgr->workers[isc_nm_tid()],
+ (isc__netievent_t *)&ievent);
+ }
+}
+
+void
+isc__nmsocket_barrier_init(isc_nmsocket_t *listener) {
+ REQUIRE(listener->nchildren > 0);
+ isc_barrier_init(&listener->barrier, listener->nchildren);
+ listener->barrier_initialised = true;
+}
+
+void
+isc__nm_async_sockstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_sockstop_t *ievent = (isc__netievent_sockstop_t *)ev0;
+ isc_nmsocket_t *listener = ievent->sock;
+ UNUSED(worker);
+
+ (void)atomic_fetch_sub(&listener->rchildren, 1);
+ isc_barrier_wait(&listener->barrier);
+
+ if (listener->tid != isc_nm_tid()) {
+ return;
+ }
+
+ if (!atomic_compare_exchange_strong(&listener->listening,
+ &(bool){ true }, false))
+ {
+ UNREACHABLE();
+ }
+
+ INSIST(atomic_load(&listener->rchildren) == 0);
+
+ listener->accept_cb = NULL;
+ listener->accept_cbarg = NULL;
+ listener->recv_cb = NULL;
+ listener->recv_cbarg = NULL;
+
+ if (listener->outer != NULL) {
+ isc_nm_stoplistening(listener->outer);
+ isc__nmsocket_detach(&listener->outer);
+ }
+
+ atomic_store(&listener->closed, true);
+}
+
+void
+isc__nm_connectcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult, bool async) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+
+ if (!async) {
+ isc__netievent_connectcb_t ievent = { .sock = sock,
+ .req = uvreq,
+ .result = eresult };
+ isc__nm_async_connectcb(NULL, (isc__netievent_t *)&ievent);
+ } else {
+ isc__netievent_connectcb_t *ievent =
+ isc__nm_get_netievent_connectcb(sock->mgr, sock, uvreq,
+ eresult);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_connectcb(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_connectcb_t *ievent = (isc__netievent_connectcb_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+ isc_result_t eresult = ievent->result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+ REQUIRE(uvreq->cb.connect != NULL);
+
+ uvreq->cb.connect(uvreq->handle, eresult, uvreq->cbarg);
+
+ isc__nm_uvreq_put(&uvreq, sock);
+}
+
+void
+isc__nm_readcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+
+ if (eresult == ISC_R_SUCCESS || eresult == ISC_R_TIMEDOUT) {
+ isc__netievent_readcb_t ievent = { .sock = sock,
+ .req = uvreq,
+ .result = eresult };
+
+ isc__nm_async_readcb(NULL, (isc__netievent_t *)&ievent);
+ } else {
+ isc__netievent_readcb_t *ievent = isc__nm_get_netievent_readcb(
+ sock->mgr, sock, uvreq, eresult);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_readcb(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_readcb_t *ievent = (isc__netievent_readcb_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+ isc_result_t eresult = ievent->result;
+ isc_region_t region;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ region.base = (unsigned char *)uvreq->uvbuf.base;
+ region.length = uvreq->uvbuf.len;
+
+ uvreq->cb.recv(uvreq->handle, eresult, &region, uvreq->cbarg);
+
+ isc__nm_uvreq_put(&uvreq, sock);
+}
+
+void
+isc__nm_sendcb(isc_nmsocket_t *sock, isc__nm_uvreq_t *uvreq,
+ isc_result_t eresult, bool async) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+
+ if (!async) {
+ isc__netievent_sendcb_t ievent = { .sock = sock,
+ .req = uvreq,
+ .result = eresult };
+ isc__nm_async_sendcb(NULL, (isc__netievent_t *)&ievent);
+ return;
+ }
+
+ isc__netievent_sendcb_t *ievent =
+ isc__nm_get_netievent_sendcb(sock->mgr, sock, uvreq, eresult);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_sendcb(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_sendcb_t *ievent = (isc__netievent_sendcb_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+ isc_result_t eresult = ievent->result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ uvreq->cb.send(uvreq->handle, eresult, uvreq->cbarg);
+
+ isc__nm_uvreq_put(&uvreq, sock);
+}
+
+static void
+isc__nm_async_close(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_close_t *ievent = (isc__netievent_close_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->closehandle_cb != NULL);
+
+ UNUSED(worker);
+
+ ievent->sock->closehandle_cb(sock);
+}
+
+void
+isc__nm_async_detach(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_detach_t *ievent = (isc__netievent_detach_t *)ev0;
+ FLARG_IEVENT(ievent);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(VALID_NMHANDLE(ievent->handle));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ nmhandle_detach_cb(&ievent->handle FLARG_PASS);
+}
+
+static void
+reset_shutdown(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ isc__nmsocket_shutdown(sock);
+ isc__nmsocket_detach(&sock);
+}
+
+void
+isc__nmsocket_reset(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ /*
+ * This can be called from the TCP write timeout, or
+ * from the TCPDNS or TLSDNS branches of isc_nm_bad_request().
+ */
+ REQUIRE(sock->parent == NULL);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ if (!uv_is_closing(&sock->uv_handle.handle) &&
+ uv_is_active(&sock->uv_handle.handle))
+ {
+ /*
+ * The real shutdown will be handled in the respective
+ * close functions.
+ */
+ isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL });
+ int r = uv_tcp_close_reset(&sock->uv_handle.tcp,
+ reset_shutdown);
+ UV_RUNTIME_CHECK(uv_tcp_close_reset, r);
+ } else {
+ isc__nmsocket_shutdown(sock);
+ }
+}
+
+void
+isc__nmsocket_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ isc__nm_udp_shutdown(sock);
+ break;
+ case isc_nm_tcpsocket:
+ isc__nm_tcp_shutdown(sock);
+ break;
+ case isc_nm_tcpdnssocket:
+ isc__nm_tcpdns_shutdown(sock);
+ break;
+ case isc_nm_tlsdnssocket:
+ isc__nm_tlsdns_shutdown(sock);
+ break;
+ case isc_nm_udplistener:
+ case isc_nm_tcplistener:
+ case isc_nm_tcpdnslistener:
+ case isc_nm_tlsdnslistener:
+ return;
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+shutdown_walk_cb(uv_handle_t *handle, void *arg) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ UNUSED(arg);
+
+ if (uv_is_closing(handle)) {
+ return;
+ }
+
+ switch (handle->type) {
+ case UV_UDP:
+ isc__nmsocket_shutdown(sock);
+ return;
+ case UV_TCP:
+ switch (sock->type) {
+ case isc_nm_tcpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ if (sock->parent == NULL) {
+ /* Reset the TCP connections on shutdown */
+ isc__nmsocket_reset(sock);
+ return;
+ }
+ FALLTHROUGH;
+ default:
+ isc__nmsocket_shutdown(sock);
+ }
+
+ return;
+ default:
+ return;
+ }
+}
+
+void
+isc__nm_async_shutdown(isc__networker_t *worker, isc__netievent_t *ev0) {
+ UNUSED(ev0);
+
+ uv_walk(&worker->loop, shutdown_walk_cb, NULL);
+}
+
+bool
+isc__nm_acquire_interlocked(isc_nm_t *mgr) {
+ if (!isc__nm_in_netthread()) {
+ return (false);
+ }
+
+ LOCK(&mgr->lock);
+ bool success = atomic_compare_exchange_strong(
+ &mgr->interlocked, &(int){ ISC_NETMGR_NON_INTERLOCKED },
+ isc_nm_tid());
+
+ UNLOCK(&mgr->lock);
+ return (success);
+}
+
+void
+isc__nm_drop_interlocked(isc_nm_t *mgr) {
+ if (!isc__nm_in_netthread()) {
+ return;
+ }
+
+ LOCK(&mgr->lock);
+ int tid = atomic_exchange(&mgr->interlocked,
+ ISC_NETMGR_NON_INTERLOCKED);
+ INSIST(tid != ISC_NETMGR_NON_INTERLOCKED);
+ BROADCAST(&mgr->wkstatecond);
+ UNLOCK(&mgr->lock);
+}
+
+void
+isc__nm_acquire_interlocked_force(isc_nm_t *mgr) {
+ if (!isc__nm_in_netthread()) {
+ return;
+ }
+
+ LOCK(&mgr->lock);
+ while (!atomic_compare_exchange_strong(
+ &mgr->interlocked, &(int){ ISC_NETMGR_NON_INTERLOCKED },
+ isc_nm_tid()))
+ {
+ WAIT(&mgr->wkstatecond, &mgr->lock);
+ }
+ UNLOCK(&mgr->lock);
+}
+
+void
+isc_nm_setstats(isc_nm_t *mgr, isc_stats_t *stats) {
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(mgr->stats == NULL);
+ REQUIRE(isc_stats_ncounters(stats) == isc_sockstatscounter_max);
+
+ isc_stats_attach(stats, &mgr->stats);
+}
+
+void
+isc__nm_incstats(isc_nmsocket_t *sock, isc__nm_statid_t id) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(id < STATID_MAX);
+
+ if (sock->statsindex != NULL && sock->mgr->stats != NULL) {
+ isc_stats_increment(sock->mgr->stats, sock->statsindex[id]);
+ }
+}
+
+void
+isc__nm_decstats(isc_nmsocket_t *sock, isc__nm_statid_t id) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(id < STATID_MAX);
+
+ if (sock->statsindex != NULL && sock->mgr->stats != NULL) {
+ isc_stats_decrement(sock->mgr->stats, sock->statsindex[id]);
+ }
+}
+
+isc_result_t
+isc__nm_socket(int domain, int type, int protocol, uv_os_sock_t *sockp) {
+ int sock = socket(domain, type, protocol);
+ if (sock < 0) {
+ return (isc_errno_toresult(errno));
+ }
+
+ *sockp = (uv_os_sock_t)sock;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_closesocket(uv_os_sock_t sock) {
+ close(sock);
+}
+
+#define setsockopt_on(socket, level, name) \
+ setsockopt(socket, level, name, &(int){ 1 }, sizeof(int))
+
+#define setsockopt_off(socket, level, name) \
+ setsockopt(socket, level, name, &(int){ 0 }, sizeof(int))
+
+isc_result_t
+isc__nm_socket_freebind(uv_os_sock_t fd, sa_family_t sa_family) {
+ /*
+ * Set the IP_FREEBIND (or equivalent option) on the uv_handle.
+ */
+#ifdef IP_FREEBIND
+ UNUSED(sa_family);
+ if (setsockopt_on(fd, IPPROTO_IP, IP_FREEBIND) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#elif defined(IP_BINDANY) || defined(IPV6_BINDANY)
+ if (sa_family == AF_INET) {
+#if defined(IP_BINDANY)
+ if (setsockopt_on(fd, IPPROTO_IP, IP_BINDANY) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#endif
+ } else if (sa_family == AF_INET6) {
+#if defined(IPV6_BINDANY)
+ if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_BINDANY) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#endif
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+#elif defined(SO_BINDANY)
+ UNUSED(sa_family);
+ if (setsockopt_on(fd, SOL_SOCKET, SO_BINDANY) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#else
+ UNUSED(fd);
+ UNUSED(sa_family);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_reuse(uv_os_sock_t fd) {
+ /*
+ * Generally, the SO_REUSEADDR socket option allows reuse of
+ * local addresses.
+ *
+ * On the BSDs, SO_REUSEPORT implies SO_REUSEADDR but with some
+ * additional refinements for programs that use multicast.
+ *
+ * On Linux, SO_REUSEPORT has different semantics: it _shares_ the port
+ * rather than steal it from the current listener, so we don't use it
+ * here, but rather in isc__nm_socket_reuse_lb().
+ *
+ * On Windows, it also allows a socket to forcibly bind to a port in use
+ * by another socket.
+ */
+
+#if defined(SO_REUSEPORT) && !defined(__linux__)
+ if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#elif defined(SO_REUSEADDR)
+ if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEADDR) == -1) {
+ return (ISC_R_FAILURE);
+ }
+ return (ISC_R_SUCCESS);
+#else
+ UNUSED(fd);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_reuse_lb(uv_os_sock_t fd) {
+ /*
+ * On FreeBSD 12+, SO_REUSEPORT_LB socket option allows sockets to be
+ * bound to an identical socket address. For UDP sockets, the use of
+ * this option can provide better distribution of incoming datagrams to
+ * multiple processes (or threads) as compared to the traditional
+ * technique of having multiple processes compete to receive datagrams
+ * on the same socket.
+ *
+ * On Linux, the same thing is achieved simply with SO_REUSEPORT.
+ */
+#if defined(SO_REUSEPORT_LB)
+ if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT_LB) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#elif defined(SO_REUSEPORT) && defined(__linux__)
+ if (setsockopt_on(fd, SOL_SOCKET, SO_REUSEPORT) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_incoming_cpu(uv_os_sock_t fd) {
+#ifdef SO_INCOMING_CPU
+ if (setsockopt_on(fd, SOL_SOCKET, SO_INCOMING_CPU) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+#endif
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+isc__nm_socket_disable_pmtud(uv_os_sock_t fd, sa_family_t sa_family) {
+ /*
+ * Disable the Path MTU Discovery on IP packets
+ */
+ if (sa_family == AF_INET6) {
+#if defined(IPV6_DONTFRAG)
+ if (setsockopt_off(fd, IPPROTO_IPV6, IPV6_DONTFRAG) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#elif defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT)
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
+ &(int){ IP_PMTUDISC_OMIT }, sizeof(int)) == -1)
+ {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+#endif
+ } else if (sa_family == AF_INET) {
+#if defined(IP_DONTFRAG)
+ if (setsockopt_off(fd, IPPROTO_IP, IP_DONTFRAG) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#elif defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_OMIT)
+ if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER,
+ &(int){ IP_PMTUDISC_OMIT }, sizeof(int)) == -1)
+ {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+#endif
+ } else {
+ return (ISC_R_FAMILYNOSUPPORT);
+ }
+
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+isc__nm_socket_v6only(uv_os_sock_t fd, sa_family_t sa_family) {
+ /*
+ * Enable the IPv6-only option on IPv6 sockets
+ */
+ if (sa_family == AF_INET6) {
+#if defined(IPV6_V6ONLY)
+ if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_V6ONLY) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+#endif
+ }
+ return (ISC_R_NOTIMPLEMENTED);
+}
+
+isc_result_t
+isc_nm_checkaddr(const isc_sockaddr_t *addr, isc_socktype_t type) {
+ int proto, pf, addrlen, fd, r;
+
+ REQUIRE(addr != NULL);
+
+ switch (type) {
+ case isc_socktype_tcp:
+ proto = SOCK_STREAM;
+ break;
+ case isc_socktype_udp:
+ proto = SOCK_DGRAM;
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ pf = isc_sockaddr_pf(addr);
+ if (pf == AF_INET) {
+ addrlen = sizeof(struct sockaddr_in);
+ } else {
+ addrlen = sizeof(struct sockaddr_in6);
+ }
+
+ fd = socket(pf, proto, 0);
+ if (fd < 0) {
+ return (isc_errno_toresult(errno));
+ }
+
+ r = bind(fd, (const struct sockaddr *)&addr->type.sa, addrlen);
+ if (r < 0) {
+ close(fd);
+ return (isc_errno_toresult(errno));
+ }
+
+ close(fd);
+ return (ISC_R_SUCCESS);
+}
+
+#if defined(TCP_CONNECTIONTIMEOUT)
+#define TIMEOUT_TYPE int
+#define TIMEOUT_DIV 1000
+#define TIMEOUT_OPTNAME TCP_CONNECTIONTIMEOUT
+#elif defined(TCP_RXT_CONNDROPTIME)
+#define TIMEOUT_TYPE int
+#define TIMEOUT_DIV 1000
+#define TIMEOUT_OPTNAME TCP_RXT_CONNDROPTIME
+#elif defined(TCP_USER_TIMEOUT)
+#define TIMEOUT_TYPE unsigned int
+#define TIMEOUT_DIV 1
+#define TIMEOUT_OPTNAME TCP_USER_TIMEOUT
+#elif defined(TCP_KEEPINIT)
+#define TIMEOUT_TYPE int
+#define TIMEOUT_DIV 1000
+#define TIMEOUT_OPTNAME TCP_KEEPINIT
+#endif
+
+isc_result_t
+isc__nm_socket_connectiontimeout(uv_os_sock_t fd, int timeout_ms) {
+#if defined(TIMEOUT_OPTNAME)
+ TIMEOUT_TYPE timeout = timeout_ms / TIMEOUT_DIV;
+
+ if (timeout == 0) {
+ timeout = 1;
+ }
+
+ if (setsockopt(fd, IPPROTO_TCP, TIMEOUT_OPTNAME, &timeout,
+ sizeof(timeout)) == -1)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ return (ISC_R_SUCCESS);
+#else
+ UNUSED(fd);
+ UNUSED(timeout_ms);
+
+ return (ISC_R_SUCCESS);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_tcp_nodelay(uv_os_sock_t fd) {
+#ifdef TCP_NODELAY
+ if (setsockopt_on(fd, IPPROTO_TCP, TCP_NODELAY) == -1) {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+ return (ISC_R_SUCCESS);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_tcp_maxseg(uv_os_sock_t fd, int size) {
+#ifdef TCP_MAXSEG
+ if (setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, (void *)&size,
+ sizeof(size)))
+ {
+ return (ISC_R_FAILURE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+#else
+ UNUSED(fd);
+ UNUSED(size);
+ return (ISC_R_SUCCESS);
+#endif
+}
+
+isc_result_t
+isc__nm_socket_min_mtu(uv_os_sock_t fd, sa_family_t sa_family) {
+ if (sa_family != AF_INET6) {
+ return (ISC_R_SUCCESS);
+ }
+#ifdef IPV6_USE_MIN_MTU
+ if (setsockopt_on(fd, IPPROTO_IPV6, IPV6_USE_MIN_MTU) == -1) {
+ return (ISC_R_FAILURE);
+ }
+#elif defined(IPV6_MTU)
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU, &(int){ 1280 },
+ sizeof(int)) == -1)
+ {
+ return (ISC_R_FAILURE);
+ }
+#else
+ UNUSED(fd);
+#endif
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_set_network_buffers(isc_nm_t *nm, uv_handle_t *handle) {
+ int32_t recv_buffer_size = 0;
+ int32_t send_buffer_size = 0;
+
+ switch (handle->type) {
+ case UV_TCP:
+ recv_buffer_size =
+ atomic_load_relaxed(&nm->recv_tcp_buffer_size);
+ send_buffer_size =
+ atomic_load_relaxed(&nm->send_tcp_buffer_size);
+ break;
+ case UV_UDP:
+ recv_buffer_size =
+ atomic_load_relaxed(&nm->recv_udp_buffer_size);
+ send_buffer_size =
+ atomic_load_relaxed(&nm->send_udp_buffer_size);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ if (recv_buffer_size > 0) {
+ int r = uv_recv_buffer_size(handle, &recv_buffer_size);
+ UV_RUNTIME_CHECK(uv_recv_buffer_size, r);
+ }
+
+ if (send_buffer_size > 0) {
+ int r = uv_send_buffer_size(handle, &send_buffer_size);
+ UV_RUNTIME_CHECK(uv_send_buffer_size, r);
+ }
+}
+
+static isc_threadresult_t
+isc__nm_work_run(isc_threadarg_t arg) {
+ isc__nm_work_t *work = (isc__nm_work_t *)arg;
+
+ work->cb(work->data);
+
+ return ((isc_threadresult_t)0);
+}
+
+static void
+isc__nm_work_cb(uv_work_t *req) {
+ isc__nm_work_t *work = uv_req_get_data((uv_req_t *)req);
+
+ if (isc_tid_v == SIZE_MAX) {
+ isc__trampoline_t *trampoline_arg =
+ isc__trampoline_get(isc__nm_work_run, work);
+ (void)isc__trampoline_run(trampoline_arg);
+ } else {
+ (void)isc__nm_work_run((isc_threadarg_t)work);
+ }
+}
+
+static void
+isc__nm_after_work_cb(uv_work_t *req, int status) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc__nm_work_t *work = uv_req_get_data((uv_req_t *)req);
+ isc_nm_t *netmgr = work->netmgr;
+
+ if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ }
+
+ work->after_cb(work->data, result);
+
+ isc_mem_put(netmgr->mctx, work, sizeof(*work));
+
+ isc_nm_detach(&netmgr);
+}
+
+void
+isc_nm_work_offload(isc_nm_t *netmgr, isc_nm_workcb_t work_cb,
+ isc_nm_after_workcb_t after_work_cb, void *data) {
+ isc__networker_t *worker = NULL;
+ isc__nm_work_t *work = NULL;
+ int r;
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(VALID_NM(netmgr));
+
+ worker = &netmgr->workers[isc_nm_tid()];
+
+ work = isc_mem_get(netmgr->mctx, sizeof(*work));
+ *work = (isc__nm_work_t){
+ .cb = work_cb,
+ .after_cb = after_work_cb,
+ .data = data,
+ };
+
+ isc_nm_attach(netmgr, &work->netmgr);
+
+ uv_req_set_data((uv_req_t *)&work->req, work);
+
+ r = uv_queue_work(&worker->loop, &work->req, isc__nm_work_cb,
+ isc__nm_after_work_cb);
+ UV_RUNTIME_CHECK(uv_queue_work, r);
+}
+
+void
+isc_nm_sequential(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ break;
+ case isc_nm_httpsocket:
+ return;
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * We don't want pipelining on this connection. That means
+ * that we need to pause after reading each request, and
+ * resume only after the request has been processed. This
+ * is done in isc__nm_resume_processing(), which is the
+ * socket's closehandle_cb callback, called whenever a handle
+ * is released.
+ */
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+ atomic_store(&sock->sequential, true);
+}
+
+void
+isc_nm_bad_request(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_udpsocket:
+ return;
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ REQUIRE(sock->parent == NULL);
+ isc__nmsocket_reset(sock);
+ return;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httpsocket:
+ isc__nm_http_bad_request(handle);
+ return;
+#endif /* HAVE_LIBNGHTTP2 */
+ case isc_nm_tcpsocket:
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+isc_result_t
+isc_nm_xfr_checkperm(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc_result_t result = ISC_R_NOPERM;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+
+ switch (sock->type) {
+ case isc_nm_tcpdnssocket:
+ result = ISC_R_SUCCESS;
+ break;
+ case isc_nm_tlsdnssocket:
+ result = isc__nm_tlsdns_xfr_checkperm(sock);
+ break;
+ default:
+ break;
+ }
+
+ return (result);
+}
+
+bool
+isc_nm_is_http_handle(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ return (handle->sock->type == isc_nm_httpsocket);
+}
+
+void
+isc_nm_set_maxage(isc_nmhandle_t *handle, const uint32_t ttl) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(!atomic_load(&handle->sock->client));
+
+#if !HAVE_LIBNGHTTP2
+ UNUSED(ttl);
+#endif
+
+ sock = handle->sock;
+ switch (sock->type) {
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httpsocket:
+ isc__nm_http_set_maxage(handle, ttl);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ case isc_nm_udpsocket:
+ case isc_nm_tcpdnssocket:
+ case isc_nm_tlsdnssocket:
+ return;
+ break;
+
+ case isc_nm_tcpsocket:
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ UNREACHABLE();
+ break;
+ }
+}
+
+isc_nmsocket_type
+isc_nm_socket_type(const isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ return (handle->sock->type);
+}
+
+bool
+isc_nm_has_encryption(const isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ switch (handle->sock->type) {
+ case isc_nm_tlsdnssocket:
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+#endif /* HAVE_LIBNGHTTP2 */
+ return (true);
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httpsocket:
+ return (isc__nm_http_has_encryption(handle));
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ return (false);
+ };
+
+ return (false);
+}
+
+void
+isc__nm_async_settlsctx(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent__tlsctx_t *ev_tlsctx = (isc__netievent__tlsctx_t *)ev0;
+ const int tid = isc_nm_tid();
+ isc_nmsocket_t *listener = ev_tlsctx->sock;
+ isc_tlsctx_t *tlsctx = ev_tlsctx->tlsctx;
+
+ UNUSED(worker);
+
+ switch (listener->type) {
+ case isc_nm_tlsdnslistener:
+ isc__nm_async_tlsdns_set_tlsctx(listener, tlsctx, tid);
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlslistener:
+ isc__nm_async_tls_set_tlsctx(listener, tlsctx, tid);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ UNREACHABLE();
+ break;
+ };
+}
+
+static void
+set_tlsctx_workers(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) {
+ /* Update the TLS context reference for every worker thread. */
+ for (size_t i = 0; i < (size_t)listener->mgr->nworkers; i++) {
+ isc__netievent__tlsctx_t *ievent =
+ isc__nm_get_netievent_settlsctx(listener->mgr, listener,
+ tlsctx);
+ isc__nm_enqueue_ievent(&listener->mgr->workers[i],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc_nmsocket_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx) {
+ REQUIRE(VALID_NMSOCK(listener));
+ REQUIRE(tlsctx != NULL);
+
+ switch (listener->type) {
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httplistener:
+ /*
+ * We handle HTTP listener sockets differently, as they rely
+ * on underlying TLS sockets for networking. The TLS context
+ * will get passed to these underlying sockets via the call to
+ * isc__nm_http_set_tlsctx().
+ */
+ isc__nm_http_set_tlsctx(listener, tlsctx);
+ break;
+ case isc_nm_tlslistener:
+ set_tlsctx_workers(listener, tlsctx);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ case isc_nm_tlsdnslistener:
+ set_tlsctx_workers(listener, tlsctx);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ };
+}
+
+const char *
+isc_nm_verify_tls_peer_result_string(const isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+ switch (sock->type) {
+ case isc_nm_tlsdnssocket:
+ return (isc__nm_tlsdns_verify_tls_peer_result_string(handle));
+ break;
+#if HAVE_LIBNGHTTP2
+ case isc_nm_tlssocket:
+ return (isc__nm_tls_verify_tls_peer_result_string(handle));
+ break;
+ case isc_nm_httpsocket:
+ return (isc__nm_http_verify_tls_peer_result_string(handle));
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ break;
+ }
+
+ return (NULL);
+}
+
+void
+isc_nmsocket_set_max_streams(isc_nmsocket_t *listener,
+ const uint32_t max_streams) {
+ REQUIRE(VALID_NMSOCK(listener));
+ switch (listener->type) {
+#if HAVE_LIBNGHTTP2
+ case isc_nm_httplistener:
+ isc__nm_http_set_max_streams(listener, max_streams);
+ break;
+#endif /* HAVE_LIBNGHTTP2 */
+ default:
+ UNUSED(max_streams);
+ break;
+ };
+ return;
+}
+
+void
+isc__nmsocket_log_tls_session_reuse(isc_nmsocket_t *sock, isc_tls_t *tls) {
+ const int log_level = ISC_LOG_DEBUG(1);
+ char client_sabuf[ISC_SOCKADDR_FORMATSIZE];
+ char local_sabuf[ISC_SOCKADDR_FORMATSIZE];
+
+ REQUIRE(tls != NULL);
+
+ if (!isc_log_wouldlog(isc_lctx, log_level)) {
+ return;
+ };
+
+ isc_sockaddr_format(&sock->peer, client_sabuf, sizeof(client_sabuf));
+ isc_sockaddr_format(&sock->iface, local_sabuf, sizeof(local_sabuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ log_level, "TLS %s session %s for %s on %s",
+ SSL_is_server(tls) ? "server" : "client",
+ SSL_session_reused(tls) ? "resumed" : "created",
+ client_sabuf, local_sabuf);
+}
+
+#ifdef NETMGR_TRACE
+/*
+ * Dump all active sockets in netmgr. We output to stderr
+ * as the logger might be already shut down.
+ */
+
+static const char *
+nmsocket_type_totext(isc_nmsocket_type type) {
+ switch (type) {
+ case isc_nm_udpsocket:
+ return ("isc_nm_udpsocket");
+ case isc_nm_udplistener:
+ return ("isc_nm_udplistener");
+ case isc_nm_tcpsocket:
+ return ("isc_nm_tcpsocket");
+ case isc_nm_tcplistener:
+ return ("isc_nm_tcplistener");
+ case isc_nm_tcpdnslistener:
+ return ("isc_nm_tcpdnslistener");
+ case isc_nm_tcpdnssocket:
+ return ("isc_nm_tcpdnssocket");
+ case isc_nm_tlssocket:
+ return ("isc_nm_tlssocket");
+ case isc_nm_tlslistener:
+ return ("isc_nm_tlslistener");
+ case isc_nm_tlsdnslistener:
+ return ("isc_nm_tlsdnslistener");
+ case isc_nm_tlsdnssocket:
+ return ("isc_nm_tlsdnssocket");
+ case isc_nm_httplistener:
+ return ("isc_nm_httplistener");
+ case isc_nm_httpsocket:
+ return ("isc_nm_httpsocket");
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+nmhandle_dump(isc_nmhandle_t *handle) {
+ fprintf(stderr, "Active handle %p, refs %" PRIuFAST32 "\n", handle,
+ isc_refcount_current(&handle->references));
+ fprintf(stderr, "Created by:\n");
+ isc_backtrace_symbols_fd(handle->backtrace, handle->backtrace_size,
+ STDERR_FILENO);
+ fprintf(stderr, "\n\n");
+}
+
+static void
+nmsocket_dump(isc_nmsocket_t *sock) {
+ isc_nmhandle_t *handle = NULL;
+
+ LOCK(&sock->lock);
+ fprintf(stderr, "\n=================\n");
+ fprintf(stderr, "Active %s socket %p, type %s, refs %" PRIuFAST32 "\n",
+ atomic_load(&sock->client) ? "client" : "server", sock,
+ nmsocket_type_totext(sock->type),
+ isc_refcount_current(&sock->references));
+ fprintf(stderr,
+ "Parent %p, listener %p, server %p, statichandle = "
+ "%p\n",
+ sock->parent, sock->listener, sock->server, sock->statichandle);
+ fprintf(stderr, "Flags:%s%s%s%s%s\n",
+ atomic_load(&sock->active) ? " active" : "",
+ atomic_load(&sock->closing) ? " closing" : "",
+ atomic_load(&sock->destroying) ? " destroying" : "",
+ atomic_load(&sock->connecting) ? " connecting" : "",
+ atomic_load(&sock->accepting) ? " accepting" : "");
+ fprintf(stderr, "Created by:\n");
+ isc_backtrace_symbols_fd(sock->backtrace, sock->backtrace_size,
+ STDERR_FILENO);
+ fprintf(stderr, "\n");
+
+ for (handle = ISC_LIST_HEAD(sock->active_handles); handle != NULL;
+ handle = ISC_LIST_NEXT(handle, active_link))
+ {
+ static bool first = true;
+ if (first) {
+ fprintf(stderr, "Active handles:\n");
+ first = false;
+ }
+ nmhandle_dump(handle);
+ }
+
+ fprintf(stderr, "\n");
+ UNLOCK(&sock->lock);
+}
+
+void
+isc__nm_dump_active(isc_nm_t *nm) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NM(nm));
+
+ LOCK(&nm->lock);
+ for (sock = ISC_LIST_HEAD(nm->active_sockets); sock != NULL;
+ sock = ISC_LIST_NEXT(sock, active_link))
+ {
+ static bool first = true;
+ if (first) {
+ fprintf(stderr, "Outstanding sockets\n");
+ first = false;
+ }
+ nmsocket_dump(sock);
+ }
+ UNLOCK(&nm->lock);
+}
+#endif
diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c
new file mode 100644
index 0000000..2a644fe
--- /dev/null
+++ b/lib/isc/netmgr/tcp.c
@@ -0,0 +1,1456 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <libgen.h>
+#include <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/errno.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stdtime.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+#include "uv-compat.h"
+
+static atomic_uint_fast32_t last_tcpquota_log = 0;
+
+static bool
+can_log_tcp_quota(void) {
+ isc_stdtime_t now, last;
+
+ isc_stdtime_get(&now);
+ last = atomic_exchange_relaxed(&last_tcpquota_log, now);
+ if (now != last) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
+
+static void
+tcp_close_direct(isc_nmsocket_t *sock);
+
+static isc_result_t
+tcp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
+static void
+tcp_connect_cb(uv_connect_t *uvreq, int status);
+
+static void
+tcp_connection_cb(uv_stream_t *server, int status);
+
+static void
+tcp_close_cb(uv_handle_t *uvhandle);
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota);
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0);
+
+static void
+failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult);
+
+static void
+stop_tcp_parent(isc_nmsocket_t *sock);
+static void
+stop_tcp_child(isc_nmsocket_t *sock);
+
+static void
+failed_accept_cb(isc_nmsocket_t *sock, isc_result_t eresult) {
+ REQUIRE(atomic_load(&sock->accepting));
+ REQUIRE(sock->server);
+
+ /*
+ * Detach the quota early to make room for other connections;
+ * otherwise it'd be detached later asynchronously, and clog
+ * the quota unnecessarily.
+ */
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ isc__nmsocket_detach(&sock->server);
+
+ atomic_store(&sock->accepting, false);
+
+ switch (eresult) {
+ case ISC_R_NOTCONNECTED:
+ /* IGNORE: The client disconnected before we could accept */
+ break;
+ default:
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+ "Accepting TCP connection failed: %s",
+ isc_result_totext(eresult));
+ }
+}
+
+static isc_result_t
+tcp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc__networker_t *worker = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ worker = &sock->mgr->workers[sock->tid];
+
+ atomic_store(&sock->connecting, true);
+
+ /* 2 minute timeout */
+ result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r != 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (req->local.length != 0) {
+ r = uv_tcp_bind(&sock->uv_handle.tcp, &req->local.type.sa, 0);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ uv_handle_set_data(&req->uv_req.handle, req);
+ r = uv_tcp_connect(&req->uv_req.connect, &sock->uv_handle.tcp,
+ &req->peer.type.sa, tcp_connect_cb);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_CONNECTFAIL);
+ goto done;
+ }
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer,
+ &req->uv_req.connect);
+ isc__nmsocket_timer_start(sock);
+
+ atomic_store(&sock->connected, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+ LOCK(&sock->lock);
+ sock->result = result;
+ SIGNAL(&sock->cond);
+ if (!atomic_load(&sock->active)) {
+ WAIT(&sock->scond, &sock->lock);
+ }
+ INSIST(atomic_load(&sock->active));
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+void
+isc__nm_async_tcpconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpconnect_t *ievent =
+ (isc__netievent_tcpconnect_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(sock->parent == NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = tcp_connect_direct(sock, req);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->active, false);
+ if (sock->fd != (uv_os_sock_t)(-1)) {
+ isc__nm_tcp_close(sock);
+ }
+ isc__nm_connectcb(sock, req, result, true);
+ }
+
+ /*
+ * The sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tcp_connect_cb(uv_connect_t *uvreq, int status) {
+ isc_result_t result = ISC_R_UNSET;
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle);
+ struct sockaddr_storage ss;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ req = uv_handle_get_data((uv_handle_t *)uvreq);
+
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_NMHANDLE(req->handle));
+
+ if (atomic_load(&sock->timedout)) {
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (!atomic_load(&sock->connecting)) {
+ /*
+ * The connect was cancelled from timeout; just clean up
+ * the req.
+ */
+ isc__nm_uvreq_put(&req, sock);
+ return;
+ } else if (isc__nm_closing(sock)) {
+ /* Network manager shutting down */
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ } else if (isc__nmsocket_closing(sock)) {
+ /* Connection canceled */
+ result = ISC_R_CANCELED;
+ goto error;
+ } else if (status == UV_ETIMEDOUT) {
+ /* Timeout status code here indicates hard error */
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (status == UV_EADDRINUSE) {
+ /*
+ * On FreeBSD the TCP connect() call sometimes results in a
+ * spurious transient EADDRINUSE. Try a few more times before
+ * giving up.
+ */
+ if (--req->connect_tries > 0) {
+ r = uv_tcp_connect(&req->uv_req.connect,
+ &sock->uv_handle.tcp,
+ &req->peer.type.sa, tcp_connect_cb);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+ return;
+ }
+ result = isc__nm_uverr2result(status);
+ goto error;
+ } else if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto error;
+ }
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ isc__nm_incstats(sock, STATID_CONNECT);
+ r = uv_tcp_getpeername(&sock->uv_handle.tcp, (struct sockaddr *)&ss,
+ &(int){ sizeof(ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+
+ atomic_store(&sock->connecting, false);
+
+ result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc__nm_connectcb(sock, req, ISC_R_SUCCESS, false);
+
+ return;
+error:
+ isc__nm_failed_connect_cb(sock, req, result, false);
+}
+
+void
+isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tcpconnect_t *ievent = NULL;
+ isc__nm_uvreq_t *req = NULL;
+ sa_family_t sa_family;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(local != NULL);
+ REQUIRE(peer != NULL);
+
+ sa_family = peer->type.sa.sa_family;
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tcpsocket, local);
+
+ sock->extrahandlesize = extrahandlesize;
+ sock->connect_timeout = timeout;
+ sock->result = ISC_R_UNSET;
+ sock->fd = (uv_os_sock_t)-1;
+ atomic_init(&sock->client, true);
+
+ req = isc__nm_uvreq_get(mgr, sock);
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->peer = *peer;
+ req->local = *local;
+ req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface);
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, false);
+ } else {
+ isc__nmsocket_clearcb(sock);
+ sock->tid = isc_random_uniform(mgr->nworkers);
+ isc__nm_connectcb(sock, req, result, true);
+ }
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+ return;
+ }
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ ievent = isc__nm_get_netievent_tcpconnect(mgr, sock, req);
+
+ if (isc__nm_in_netthread()) {
+ atomic_store(&sock->active, true);
+ sock->tid = isc_nm_tid();
+ isc__nm_async_tcpconnect(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ isc__nm_put_netievent_tcpconnect(mgr, ievent);
+ } else {
+ atomic_init(&sock->active, false);
+ sock->tid = isc_random_uniform(mgr->nworkers);
+ isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+ LOCK(&sock->lock);
+ while (sock->result == ISC_R_UNSET) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ atomic_store(&sock->active, true);
+ BROADCAST(&sock->scond);
+ UNLOCK(&sock->lock);
+}
+
+static uv_os_sock_t
+isc__nm_tcp_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) {
+ isc_result_t result;
+ uv_os_sock_t sock;
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ (void)isc__nm_socket_incoming_cpu(sock);
+ (void)isc__nm_socket_v6only(sock, sa_family);
+
+ /* FIXME: set mss */
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ return (sock);
+}
+
+static void
+start_tcp_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock,
+ uv_os_sock_t fd, int tid) {
+ isc__netievent_tcplisten_t *ievent = NULL;
+ isc_nmsocket_t *csock = &sock->children[tid];
+
+ isc__nmsocket_init(csock, mgr, isc_nm_tcpsocket, iface);
+ csock->parent = sock;
+ csock->accept_cb = sock->accept_cb;
+ csock->accept_cbarg = sock->accept_cbarg;
+ csock->extrahandlesize = sock->extrahandlesize;
+ csock->backlog = sock->backlog;
+ csock->tid = tid;
+ /*
+ * We don't attach to quota, just assign - to avoid
+ * increasing quota unnecessarily.
+ */
+ csock->pquota = sock->pquota;
+ isc_quota_cb_init(&csock->quotacb, quota_accept_cb, csock);
+
+ if (mgr->load_balance_sockets) {
+ UNUSED(fd);
+ csock->fd = isc__nm_tcp_lb_socket(mgr,
+ iface->type.sa.sa_family);
+ } else {
+ csock->fd = dup(fd);
+ }
+ REQUIRE(csock->fd >= 0);
+
+ ievent = isc__nm_get_netievent_tcplisten(mgr, csock);
+ isc__nm_maybe_enqueue_ievent(&mgr->workers[tid],
+ (isc__netievent_t *)ievent);
+}
+
+static void
+enqueue_stoplistening(isc_nmsocket_t *sock) {
+ isc__netievent_tcpstop_t *ievent =
+ isc__nm_get_netievent_tcpstop(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+isc_result_t
+isc_nm_listentcp(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_nmsocket_t **sockp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ size_t children_size = 0;
+ uv_os_sock_t fd = -1;
+
+ REQUIRE(VALID_NM(mgr));
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tcplistener, iface);
+
+ atomic_init(&sock->rchildren, 0);
+ sock->nchildren = mgr->nworkers;
+ children_size = sock->nchildren * sizeof(sock->children[0]);
+ sock->children = isc_mem_get(mgr->mctx, children_size);
+ memset(sock->children, 0, children_size);
+
+ sock->result = ISC_R_UNSET;
+
+ sock->accept_cb = accept_cb;
+ sock->accept_cbarg = accept_cbarg;
+ sock->extrahandlesize = extrahandlesize;
+ sock->backlog = backlog;
+ sock->pquota = quota;
+
+ sock->tid = 0;
+ sock->fd = -1;
+
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_tcp_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+
+ isc_barrier_init(&sock->startlistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ if ((int)i == isc_nm_tid()) {
+ continue;
+ }
+ start_tcp_child(mgr, iface, sock, fd, i);
+ }
+
+ if (isc__nm_in_netthread()) {
+ start_tcp_child(mgr, iface, sock, fd, isc_nm_tid());
+ }
+
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+
+ LOCK(&sock->lock);
+ while (atomic_load(&sock->rchildren) != sock->nchildren) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ result = sock->result;
+ atomic_store(&sock->active, true);
+ UNLOCK(&sock->lock);
+
+ INSIST(result != ISC_R_UNSET);
+
+ if (result == ISC_R_SUCCESS) {
+ REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren);
+ *sockp = sock;
+ } else {
+ atomic_store(&sock->active, false);
+ enqueue_stoplistening(sock);
+ isc_nmsocket_close(&sock);
+ }
+
+ return (result);
+}
+
+void
+isc__nm_async_tcplisten(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcplisten_t *ievent = (isc__netievent_tcplisten_t *)ev0;
+ sa_family_t sa_family;
+ int r;
+ int flags = 0;
+ isc_nmsocket_t *sock = NULL;
+ isc_result_t result;
+ isc_nm_t *mgr;
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+ REQUIRE(VALID_NMSOCK(ievent->sock->parent));
+
+ sock = ievent->sock;
+ sa_family = sock->iface.type.sa.sa_family;
+ mgr = sock->mgr;
+
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(sock->parent != NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+ /* This keeps the socket alive after everything else is gone */
+ isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL });
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ LOCK(&sock->parent->lock);
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r < 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (sa_family == AF_INET6) {
+ flags = UV_TCP_IPV6ONLY;
+ }
+
+ if (mgr->load_balance_sockets) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ } else {
+ if (sock->parent->fd == -1) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ sock->parent->uv_handle.tcp.flags =
+ sock->uv_handle.tcp.flags;
+ sock->parent->fd = sock->fd;
+ } else {
+ /* The socket is already bound, just copy the flags */
+ sock->uv_handle.tcp.flags =
+ sock->parent->uv_handle.tcp.flags;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ /*
+ * The callback will run in the same thread uv_listen() was called
+ * from, so a race with tcp_connection_cb() isn't possible.
+ */
+ r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog,
+ tcp_connection_cb);
+ if (r != 0) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+ "uv_listen failed: %s",
+ isc_result_totext(isc__nm_uverr2result(r)));
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+
+ atomic_store(&sock->listening, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+ if (result != ISC_R_SUCCESS) {
+ sock->pquota = NULL;
+ }
+
+ atomic_fetch_add(&sock->parent->rchildren, 1);
+ if (sock->parent->result == ISC_R_UNSET) {
+ sock->parent->result = result;
+ }
+ SIGNAL(&sock->parent->cond);
+ UNLOCK(&sock->parent->lock);
+
+ isc_barrier_wait(&sock->parent->startlistening);
+}
+
+static void
+tcp_connection_cb(uv_stream_t *server, int status) {
+ isc_nmsocket_t *ssock = uv_handle_get_data((uv_handle_t *)server);
+ isc_result_t result;
+ isc_quota_t *quota = NULL;
+
+ if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto done;
+ }
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ result = ISC_R_CANCELED;
+ goto done;
+ }
+
+ if (ssock->pquota != NULL) {
+ result = isc_quota_attach_cb(ssock->pquota, &quota,
+ &ssock->quotacb);
+ if (result == ISC_R_QUOTA) {
+ isc__nm_incstats(ssock, STATID_ACCEPTFAIL);
+ goto done;
+ }
+ }
+
+ result = accept_connection(ssock, quota);
+done:
+ isc__nm_accept_connection_log(result, can_log_tcp_quota());
+}
+
+void
+isc__nm_tcp_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcplistener);
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ if (!isc__nm_in_netthread()) {
+ enqueue_stoplistening(sock);
+ } else {
+ stop_tcp_parent(sock);
+ }
+}
+
+void
+isc__nm_async_tcpstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpstop_t *ievent = (isc__netievent_tcpstop_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->parent != NULL) {
+ stop_tcp_child(sock);
+ return;
+ }
+
+ stop_tcp_parent(sock);
+}
+
+void
+isc__nm_tcp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ if (!sock->recv_read) {
+ goto destroy;
+ }
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_readcb(sock, req, result);
+ }
+
+destroy:
+ isc__nmsocket_prep_destroy(sock);
+
+ /*
+ * We need to detach from quota after the read callback function had a
+ * chance to be executed.
+ */
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+}
+
+void
+isc__nm_tcp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+ isc__netievent_tcpstartread_t *ievent = NULL;
+
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(sock->statichandle == handle);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+ if (sock->read_timeout == 0) {
+ sock->read_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ ievent = isc__nm_get_netievent_tcpstartread(sock->mgr, sock);
+
+ /*
+ * This MUST be done asynchronously, no matter which thread we're
+ * in. The callback function for isc_nm_read() often calls
+ * isc_nm_read() again; if we tried to do that synchronously
+ * we'd clash in processbuffer() and grow the stack indefinitely.
+ */
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+void
+isc__nm_async_tcpstartread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpstartread_t *ievent =
+ (isc__netievent_tcpstartread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc_result_t result;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ if (isc__nmsocket_closing(sock)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = isc__nm_start_reading(sock);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->reading, true);
+ isc__nm_tcp_failed_read_cb(sock, result);
+ return;
+ }
+
+ isc__nmsocket_timer_start(sock);
+}
+
+void
+isc__nm_tcp_pauseread(isc_nmhandle_t *handle) {
+ isc__netievent_tcppauseread_t *ievent = NULL;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->readpaused, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ ievent = isc__nm_get_netievent_tcppauseread(sock->mgr, sock);
+
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+void
+isc__nm_async_tcppauseread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcppauseread_t *ievent =
+ (isc__netievent_tcppauseread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+}
+
+void
+isc__nm_tcp_resumeread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc__netievent_tcpstartread_t *ievent = NULL;
+ isc_nmsocket_t *sock = handle->sock;
+
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->recv_cb == NULL) {
+ /* We are no longer reading */
+ return;
+ }
+
+ if (!isc__nmsocket_active(sock)) {
+ atomic_store(&sock->reading, true);
+ isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED);
+ return;
+ }
+
+ if (!atomic_compare_exchange_strong(&sock->readpaused, &(bool){ true },
+ false))
+ {
+ return;
+ }
+
+ ievent = isc__nm_get_netievent_tcpstartread(sock->mgr, sock);
+
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_tcp_read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
+ isc__nm_uvreq_t *req = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->reading));
+ REQUIRE(buf != NULL);
+
+ if (isc__nmsocket_closing(sock)) {
+ isc__nm_tcp_failed_read_cb(sock, ISC_R_CANCELED);
+ goto free;
+ }
+
+ if (nread < 0) {
+ if (nread != UV_EOF) {
+ isc__nm_incstats(sock, STATID_RECVFAIL);
+ }
+
+ isc__nm_tcp_failed_read_cb(sock, isc__nm_uverr2result(nread));
+
+ goto free;
+ }
+
+ req = isc__nm_get_read_req(sock, NULL);
+
+ /*
+ * The callback will be called synchronously because the
+ * result is ISC_R_SUCCESS, so we don't need to retain
+ * the buffer
+ */
+ req->uvbuf.base = buf->base;
+ req->uvbuf.len = nread;
+
+ if (!atomic_load(&sock->client)) {
+ sock->read_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ isc__nm_readcb(sock, req, ISC_R_SUCCESS);
+
+ /* The readcb could have paused the reading */
+ if (atomic_load(&sock->reading)) {
+ /* The timer will be updated */
+ isc__nmsocket_timer_restart(sock);
+ }
+
+free:
+ if (nread < 0) {
+ /*
+ * The buffer may be a null buffer on error.
+ */
+ if (buf->base == NULL && buf->len == 0) {
+ return;
+ }
+ }
+
+ isc__nm_free_uvbuf(sock, buf);
+}
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0) {
+ isc_nmsocket_t *sock = (isc_nmsocket_t *)sock0;
+ isc__netievent_tcpaccept_t *ievent = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ /*
+ * Create a tcpaccept event and pass it using the async channel.
+ */
+ ievent = isc__nm_get_netievent_tcpaccept(sock->mgr, sock, quota);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+/*
+ * This is called after we get a quota_accept_cb() callback.
+ */
+void
+isc__nm_async_tcpaccept(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpaccept_t *ievent = (isc__netievent_tcpaccept_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = accept_connection(sock, ievent->quota);
+ isc__nm_accept_connection_log(result, can_log_tcp_quota());
+}
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
+ isc_nmsocket_t *csock = NULL;
+ isc__networker_t *worker = NULL;
+ int r;
+ isc_result_t result;
+ struct sockaddr_storage ss;
+ isc_sockaddr_t local;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ if (quota != NULL) {
+ isc_quota_detach(&quota);
+ }
+ return (ISC_R_CANCELED);
+ }
+
+ csock = isc_mem_get(ssock->mgr->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(csock, ssock->mgr, isc_nm_tcpsocket, &ssock->iface);
+ csock->tid = ssock->tid;
+ csock->extrahandlesize = ssock->extrahandlesize;
+ isc__nmsocket_attach(ssock, &csock->server);
+ csock->recv_cb = ssock->recv_cb;
+ csock->recv_cbarg = ssock->recv_cbarg;
+ csock->quota = quota;
+ atomic_init(&csock->accepting, true);
+
+ worker = &csock->mgr->workers[isc_nm_tid()];
+
+ r = uv_tcp_init(&worker->loop, &csock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&csock->uv_handle.handle, csock);
+
+ r = uv_timer_init(&worker->loop, &csock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&csock->read_timer, csock);
+
+ r = uv_accept(&ssock->uv_handle.stream, &csock->uv_handle.stream);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ r = uv_tcp_getpeername(&csock->uv_handle.tcp, (struct sockaddr *)&ss,
+ &(int){ sizeof(ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&csock->peer,
+ (struct sockaddr *)&ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ r = uv_tcp_getsockname(&csock->uv_handle.tcp, (struct sockaddr *)&ss,
+ &(int){ sizeof(ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&local, (struct sockaddr *)&ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ handle = isc__nmhandle_get(csock, NULL, &local);
+
+ result = ssock->accept_cb(handle, ISC_R_SUCCESS, ssock->accept_cbarg);
+ if (result != ISC_R_SUCCESS) {
+ isc_nmhandle_detach(&handle);
+ goto failure;
+ }
+
+ atomic_store(&csock->accepting, false);
+
+ isc__nm_incstats(csock, STATID_ACCEPT);
+
+ csock->read_timeout = atomic_load(&csock->mgr->init);
+
+ atomic_fetch_add(&ssock->parent->active_child_connections, 1);
+
+ /*
+ * The acceptcb needs to attach to the handle if it wants to keep the
+ * connection alive
+ */
+ isc_nmhandle_detach(&handle);
+
+ /*
+ * sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&csock);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ atomic_store(&csock->active, false);
+
+ failed_accept_cb(csock, result);
+
+ isc__nmsocket_prep_destroy(csock);
+
+ isc__nmsocket_detach(&csock);
+
+ return (result);
+}
+
+void
+isc__nm_tcp_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+ isc__netievent_tcpsend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+
+ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ isc_nmhandle_attach(handle, &uvreq->handle);
+
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+
+ ievent = isc__nm_get_netievent_tcpsend(sock->mgr, sock, uvreq);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+static void
+tcp_send_cb(uv_write_t *req, int status) {
+ isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMSOCK(uvreq->sock));
+
+ sock = uvreq->sock;
+
+ isc_nm_timer_stop(uvreq->timer);
+ isc_nm_timer_detach(&uvreq->timer);
+
+ if (status < 0) {
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq,
+ isc__nm_uverr2result(status));
+ return;
+ }
+
+ isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
+}
+
+/*
+ * Handle 'tcpsend' async event - send a packet on the socket
+ */
+void
+isc__nm_async_tcpsend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc_result_t result;
+ isc__netievent_tcpsend_t *ievent = (isc__netievent_tcpsend_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ if (sock->write_timeout == 0) {
+ sock->write_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ result = tcp_send_direct(sock, uvreq);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq, result);
+ }
+}
+
+static isc_result_t
+tcp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+
+ int r;
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+ r = uv_write(&req->uv_req.write, &sock->uv_handle.stream, &req->uvbuf,
+ 1, tcp_send_cb);
+ if (r < 0) {
+ return (isc__nm_uverr2result(r));
+ }
+
+ isc_nm_timer_create(req->handle, isc__nmsocket_writetimeout_cb, req,
+ &req->timer);
+ if (sock->write_timeout > 0) {
+ isc_nm_timer_start(req->timer, sock->write_timeout);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+tcp_stop_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ atomic_store(&sock->listening, false);
+
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tcp_close_sock(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ if (sock->server != NULL) {
+ isc__nmsocket_detach(&sock->server);
+ }
+
+ atomic_store(&sock->connected, false);
+
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tcp_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ tcp_close_sock(sock);
+}
+
+static void
+read_timer_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ if (sock->parent) {
+ uv_close(&sock->uv_handle.handle, tcp_stop_cb);
+ } else if (uv_is_closing(&sock->uv_handle.handle)) {
+ tcp_close_sock(sock);
+ } else {
+ uv_close(&sock->uv_handle.handle, tcp_close_cb);
+ }
+}
+
+static void
+stop_tcp_child(isc_nmsocket_t *sock) {
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ tcp_close_direct(sock);
+
+ atomic_fetch_sub(&sock->parent->rchildren, 1);
+
+ isc_barrier_wait(&sock->parent->stoplistening);
+}
+
+static void
+stop_tcp_parent(isc_nmsocket_t *sock) {
+ isc_nmsocket_t *csock = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tcplistener);
+
+ isc_barrier_init(&sock->stoplistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ csock = &sock->children[i];
+ REQUIRE(VALID_NMSOCK(csock));
+
+ if ((int)i == isc_nm_tid()) {
+ /*
+ * We need to schedule closing the other sockets first
+ */
+ continue;
+ }
+
+ atomic_store(&csock->active, false);
+ enqueue_stoplistening(csock);
+ }
+
+ csock = &sock->children[isc_nm_tid()];
+ atomic_store(&csock->active, false);
+ stop_tcp_child(csock);
+
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tcp_close_direct(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (sock->server != NULL) {
+ REQUIRE(VALID_NMSOCK(sock->server));
+ REQUIRE(VALID_NMSOCK(sock->server->parent));
+ if (sock->server->parent != NULL) {
+ atomic_fetch_sub(
+ &sock->server->parent->active_child_connections,
+ 1);
+ }
+ }
+
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+ uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb);
+}
+
+void
+isc__nm_tcp_close(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ if (sock->tid == isc_nm_tid()) {
+ tcp_close_direct(sock);
+ } else {
+ /*
+ * We need to create an event and pass it using async channel
+ */
+ isc__netievent_tcpclose_t *ievent =
+ isc__nm_get_netievent_tcpclose(sock->mgr, sock);
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_tcpclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpclose_t *ievent = (isc__netievent_tcpclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ tcp_close_direct(sock);
+}
+
+static void
+tcp_close_connect_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ isc__nmsocket_prep_destroy(sock);
+ isc__nmsocket_detach(&sock);
+}
+
+void
+isc__nm_tcp_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+
+ /*
+ * If the socket is active, mark it inactive and
+ * continue. If it isn't active, stop now.
+ */
+ if (!isc__nmsocket_deactivate(sock)) {
+ return;
+ }
+
+ if (atomic_load(&sock->accepting)) {
+ return;
+ }
+
+ if (atomic_load(&sock->connecting)) {
+ isc_nmsocket_t *tsock = NULL;
+ isc__nmsocket_attach(sock, &tsock);
+ uv_close(&sock->uv_handle.handle, tcp_close_connect_cb);
+ return;
+ }
+
+ if (sock->statichandle != NULL) {
+ if (isc__nm_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false);
+ } else {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ }
+ return;
+ }
+
+ /*
+ * Otherwise, we just send the socket to abyss...
+ */
+ if (sock->parent == NULL) {
+ isc__nmsocket_prep_destroy(sock);
+ }
+}
+
+void
+isc__nm_tcp_cancelread(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tcpcancel_t *ievent = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpsocket);
+
+ ievent = isc__nm_get_netievent_tcpcancel(sock->mgr, sock, handle);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tcpcancel(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpcancel_t *ievent = (isc__netievent_tcpcancel_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ uv_timer_stop(&sock->read_timer);
+
+ isc__nm_tcp_failed_read_cb(sock, ISC_R_EOF);
+}
+
+int_fast32_t
+isc__nm_tcp_listener_nactive(isc_nmsocket_t *listener) {
+ int_fast32_t nactive;
+
+ REQUIRE(VALID_NMSOCK(listener));
+
+ nactive = atomic_load(&listener->active_child_connections);
+ INSIST(nactive >= 0);
+ return (nactive);
+}
diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c
new file mode 100644
index 0000000..eda6aa6
--- /dev/null
+++ b/lib/isc/netmgr/tcpdns.c
@@ -0,0 +1,1500 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <libgen.h>
+#include <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/errno.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stdtime.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+#include "uv-compat.h"
+
+static atomic_uint_fast32_t last_tcpdnsquota_log = 0;
+
+static bool
+can_log_tcpdns_quota(void) {
+ isc_stdtime_t now, last;
+
+ isc_stdtime_get(&now);
+ last = atomic_exchange_relaxed(&last_tcpdnsquota_log, now);
+ if (now != last) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
+
+static void
+tcpdns_close_direct(isc_nmsocket_t *sock);
+
+static void
+tcpdns_connect_cb(uv_connect_t *uvreq, int status);
+
+static void
+tcpdns_connection_cb(uv_stream_t *server, int status);
+
+static void
+tcpdns_close_cb(uv_handle_t *uvhandle);
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota);
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0);
+
+static void
+stop_tcpdns_parent(isc_nmsocket_t *sock);
+static void
+stop_tcpdns_child(isc_nmsocket_t *sock);
+
+static isc_result_t
+tcpdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc__networker_t *worker = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ worker = &sock->mgr->workers[sock->tid];
+
+ atomic_store(&sock->connecting, true);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ }
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r != 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (req->local.length != 0) {
+ r = uv_tcp_bind(&sock->uv_handle.tcp, &req->local.type.sa, 0);
+ /*
+ * In case of shared socket UV_EINVAL will be returned and needs
+ * to be ignored
+ */
+ if (r != 0 && r != UV_EINVAL) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ uv_handle_set_data(&req->uv_req.handle, req);
+ r = uv_tcp_connect(&req->uv_req.connect, &sock->uv_handle.tcp,
+ &req->peer.type.sa, tcpdns_connect_cb);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_CONNECTFAIL);
+ goto done;
+ }
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer,
+ &req->uv_req.connect);
+ isc__nmsocket_timer_start(sock);
+
+ atomic_store(&sock->connected, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+error:
+ LOCK(&sock->lock);
+ sock->result = result;
+ SIGNAL(&sock->cond);
+ if (!atomic_load(&sock->active)) {
+ WAIT(&sock->scond, &sock->lock);
+ }
+ INSIST(atomic_load(&sock->active));
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+void
+isc__nm_async_tcpdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnsconnect_t *ievent =
+ (isc__netievent_tcpdnsconnect_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(sock->parent == NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = tcpdns_connect_direct(sock, req);
+ if (result != ISC_R_SUCCESS) {
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->active, false);
+ isc__nm_tcpdns_close(sock);
+ }
+
+ /*
+ * The sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tcpdns_connect_cb(uv_connect_t *uvreq, int status) {
+ isc_result_t result = ISC_R_UNSET;
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle);
+ struct sockaddr_storage ss;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ req = uv_handle_get_data((uv_handle_t *)uvreq);
+
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_NMHANDLE(req->handle));
+
+ if (atomic_load(&sock->timedout)) {
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (isc__nm_closing(sock)) {
+ /* Network manager shutting down */
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ } else if (isc__nmsocket_closing(sock)) {
+ /* Connection canceled */
+ result = ISC_R_CANCELED;
+ goto error;
+ } else if (status == UV_ETIMEDOUT) {
+ /* Timeout status code here indicates hard error */
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (status == UV_EADDRINUSE) {
+ /*
+ * On FreeBSD the TCP connect() call sometimes results in a
+ * spurious transient EADDRINUSE. Try a few more times before
+ * giving up.
+ */
+ if (--req->connect_tries > 0) {
+ r = uv_tcp_connect(
+ &req->uv_req.connect, &sock->uv_handle.tcp,
+ &req->peer.type.sa, tcpdns_connect_cb);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+ return;
+ }
+ result = isc__nm_uverr2result(status);
+ goto error;
+ } else if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto error;
+ }
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ isc__nm_incstats(sock, STATID_CONNECT);
+ r = uv_tcp_getpeername(&sock->uv_handle.tcp, (struct sockaddr *)&ss,
+ &(int){ sizeof(ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+
+ atomic_store(&sock->connecting, false);
+
+ result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ isc__nm_connectcb(sock, req, ISC_R_SUCCESS, false);
+
+ return;
+error:
+ isc__nm_failed_connect_cb(sock, req, result, false);
+}
+
+void
+isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tcpdnsconnect_t *ievent = NULL;
+ isc__nm_uvreq_t *req = NULL;
+ sa_family_t sa_family;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(local != NULL);
+ REQUIRE(peer != NULL);
+
+ sa_family = peer->type.sa.sa_family;
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tcpdnssocket, local);
+
+ sock->extrahandlesize = extrahandlesize;
+ sock->connect_timeout = timeout;
+ sock->result = ISC_R_UNSET;
+ atomic_init(&sock->client, true);
+
+ req = isc__nm_uvreq_get(mgr, sock);
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->peer = *peer;
+ req->local = *local;
+ req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface);
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ }
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+ return;
+ }
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ /* 2 minute timeout */
+ result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ ievent = isc__nm_get_netievent_tcpdnsconnect(mgr, sock, req);
+
+ if (isc__nm_in_netthread()) {
+ atomic_store(&sock->active, true);
+ sock->tid = isc_nm_tid();
+ isc__nm_async_tcpdnsconnect(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ isc__nm_put_netievent_tcpdnsconnect(mgr, ievent);
+ } else {
+ atomic_init(&sock->active, false);
+ sock->tid = isc_random_uniform(mgr->nworkers);
+ isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+
+ LOCK(&sock->lock);
+ while (sock->result == ISC_R_UNSET) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ atomic_store(&sock->active, true);
+ BROADCAST(&sock->scond);
+ UNLOCK(&sock->lock);
+}
+
+static uv_os_sock_t
+isc__nm_tcpdns_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) {
+ isc_result_t result;
+ uv_os_sock_t sock;
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ (void)isc__nm_socket_incoming_cpu(sock);
+ (void)isc__nm_socket_v6only(sock, sa_family);
+
+ /* FIXME: set mss */
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ return (sock);
+}
+
+static void
+enqueue_stoplistening(isc_nmsocket_t *sock) {
+ isc__netievent_tcpdnsstop_t *ievent =
+ isc__nm_get_netievent_tcpdnsstop(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+static void
+start_tcpdns_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock,
+ uv_os_sock_t fd, int tid) {
+ isc__netievent_tcpdnslisten_t *ievent = NULL;
+ isc_nmsocket_t *csock = &sock->children[tid];
+
+ isc__nmsocket_init(csock, mgr, isc_nm_tcpdnssocket, iface);
+ csock->parent = sock;
+ csock->accept_cb = sock->accept_cb;
+ csock->accept_cbarg = sock->accept_cbarg;
+ csock->recv_cb = sock->recv_cb;
+ csock->recv_cbarg = sock->recv_cbarg;
+ csock->extrahandlesize = sock->extrahandlesize;
+ csock->backlog = sock->backlog;
+ csock->tid = tid;
+ /*
+ * We don't attach to quota, just assign - to avoid
+ * increasing quota unnecessarily.
+ */
+ csock->pquota = sock->pquota;
+ isc_quota_cb_init(&csock->quotacb, quota_accept_cb, csock);
+
+ if (mgr->load_balance_sockets) {
+ UNUSED(fd);
+ csock->fd = isc__nm_tcpdns_lb_socket(mgr,
+ iface->type.sa.sa_family);
+ } else {
+ csock->fd = dup(fd);
+ }
+ REQUIRE(csock->fd >= 0);
+
+ ievent = isc__nm_get_netievent_tcpdnslisten(mgr, csock);
+ isc__nm_maybe_enqueue_ievent(&mgr->workers[tid],
+ (isc__netievent_t *)ievent);
+}
+isc_result_t
+isc_nm_listentcpdns(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_recv_cb_t recv_cb, void *recv_cbarg,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_nmsocket_t **sockp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ size_t children_size = 0;
+ uv_os_sock_t fd = -1;
+
+ REQUIRE(VALID_NM(mgr));
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tcpdnslistener, iface);
+
+ atomic_init(&sock->rchildren, 0);
+ sock->nchildren = mgr->nworkers;
+ children_size = sock->nchildren * sizeof(sock->children[0]);
+ sock->children = isc_mem_get(mgr->mctx, children_size);
+ memset(sock->children, 0, children_size);
+
+ sock->result = ISC_R_UNSET;
+ sock->accept_cb = accept_cb;
+ sock->accept_cbarg = accept_cbarg;
+ sock->recv_cb = recv_cb;
+ sock->recv_cbarg = recv_cbarg;
+ sock->extrahandlesize = extrahandlesize;
+ sock->backlog = backlog;
+ sock->pquota = quota;
+
+ sock->tid = 0;
+ sock->fd = -1;
+
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_tcpdns_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+
+ isc_barrier_init(&sock->startlistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ if ((int)i == isc_nm_tid()) {
+ continue;
+ }
+ start_tcpdns_child(mgr, iface, sock, fd, i);
+ }
+
+ if (isc__nm_in_netthread()) {
+ start_tcpdns_child(mgr, iface, sock, fd, isc_nm_tid());
+ }
+
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+
+ LOCK(&sock->lock);
+ while (atomic_load(&sock->rchildren) != sock->nchildren) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ result = sock->result;
+ atomic_store(&sock->active, true);
+ UNLOCK(&sock->lock);
+
+ INSIST(result != ISC_R_UNSET);
+
+ if (result == ISC_R_SUCCESS) {
+ REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren);
+ *sockp = sock;
+ } else {
+ atomic_store(&sock->active, false);
+ enqueue_stoplistening(sock);
+ isc_nmsocket_close(&sock);
+ }
+
+ return (result);
+}
+
+void
+isc__nm_async_tcpdnslisten(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnslisten_t *ievent =
+ (isc__netievent_tcpdnslisten_t *)ev0;
+ sa_family_t sa_family;
+ int r;
+ int flags = 0;
+ isc_nmsocket_t *sock = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ isc_nm_t *mgr = NULL;
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+ REQUIRE(VALID_NMSOCK(ievent->sock->parent));
+
+ sock = ievent->sock;
+ sa_family = sock->iface.type.sa.sa_family;
+ mgr = sock->mgr;
+
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(sock->parent != NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+ /* This keeps the socket alive after everything else is gone */
+ isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL });
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ LOCK(&sock->parent->lock);
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r < 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (sa_family == AF_INET6) {
+ flags = UV_TCP_IPV6ONLY;
+ }
+
+ if (mgr->load_balance_sockets) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ } else {
+ if (sock->parent->fd == -1) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ sock->parent->uv_handle.tcp.flags =
+ sock->uv_handle.tcp.flags;
+ sock->parent->fd = sock->fd;
+ } else {
+ /* The socket is already bound, just copy the flags */
+ sock->uv_handle.tcp.flags =
+ sock->parent->uv_handle.tcp.flags;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ /*
+ * The callback will run in the same thread uv_listen() was called
+ * from, so a race with tcpdns_connection_cb() isn't possible.
+ */
+ r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog,
+ tcpdns_connection_cb);
+ if (r != 0) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+ "uv_listen failed: %s",
+ isc_result_totext(isc__nm_uverr2result(r)));
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+
+ atomic_store(&sock->listening, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+ if (result != ISC_R_SUCCESS) {
+ sock->pquota = NULL;
+ }
+
+ atomic_fetch_add(&sock->parent->rchildren, 1);
+ if (sock->parent->result == ISC_R_UNSET) {
+ sock->parent->result = result;
+ }
+ SIGNAL(&sock->parent->cond);
+ UNLOCK(&sock->parent->lock);
+
+ isc_barrier_wait(&sock->parent->startlistening);
+}
+
+static void
+tcpdns_connection_cb(uv_stream_t *server, int status) {
+ isc_nmsocket_t *ssock = uv_handle_get_data((uv_handle_t *)server);
+ isc_result_t result;
+ isc_quota_t *quota = NULL;
+
+ if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto done;
+ }
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ result = ISC_R_CANCELED;
+ goto done;
+ }
+
+ if (ssock->pquota != NULL) {
+ result = isc_quota_attach_cb(ssock->pquota, &quota,
+ &ssock->quotacb);
+ if (result == ISC_R_QUOTA) {
+ isc__nm_incstats(ssock, STATID_ACCEPTFAIL);
+ goto done;
+ }
+ }
+
+ result = accept_connection(ssock, quota);
+done:
+ isc__nm_accept_connection_log(result, can_log_tcpdns_quota());
+}
+
+void
+isc__nm_tcpdns_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpdnslistener);
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ if (!isc__nm_in_netthread()) {
+ enqueue_stoplistening(sock);
+ } else {
+ stop_tcpdns_parent(sock);
+ }
+}
+
+void
+isc__nm_async_tcpdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnsstop_t *ievent =
+ (isc__netievent_tcpdnsstop_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->parent != NULL) {
+ stop_tcpdns_child(sock);
+ return;
+ }
+
+ stop_tcpdns_parent(sock);
+}
+
+void
+isc__nm_tcpdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ if (!sock->recv_read) {
+ goto destroy;
+ }
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_readcb(sock, req, result);
+ }
+
+destroy:
+ isc__nmsocket_prep_destroy(sock);
+
+ /*
+ * We need to detach from quota after the read callback function had a
+ * chance to be executed.
+ */
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+}
+
+void
+isc__nm_tcpdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+ isc__netievent_tcpdnsread_t *ievent = NULL;
+
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(sock->statichandle == handle);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+ if (sock->read_timeout == 0) {
+ sock->read_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ ievent = isc__nm_get_netievent_tcpdnsread(sock->mgr, sock);
+
+ /*
+ * This MUST be done asynchronously, no matter which thread we're
+ * in. The callback function for isc_nm_read() often calls
+ * isc_nm_read() again; if we tried to do that synchronously
+ * we'd clash in processbuffer() and grow the stack indefinitely.
+ */
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+void
+isc__nm_async_tcpdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnsread_t *ievent =
+ (isc__netievent_tcpdnsread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(sock)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = isc__nm_process_sock_buffer(sock);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->reading, true);
+ isc__nm_failed_read_cb(sock, result, false);
+ }
+}
+
+/*
+ * Process a single packet from the incoming buffer.
+ *
+ * Return ISC_R_SUCCESS and attach 'handlep' to a handle if something
+ * was processed; return ISC_R_NOMORE if there isn't a full message
+ * to be processed.
+ *
+ * The caller will need to unreference the handle.
+ */
+isc_result_t
+isc__nm_tcpdns_processbuffer(isc_nmsocket_t *sock) {
+ size_t len;
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+ /*
+ * If we don't even have the length yet, we can't do
+ * anything.
+ */
+ if (sock->buf_len < 2) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Process the first packet from the buffer, leaving
+ * the rest (if any) for later.
+ */
+ len = ntohs(*(uint16_t *)sock->buf);
+ if (len > sock->buf_len - 2) {
+ return (ISC_R_NOMORE);
+ }
+
+ if (sock->recv_cb == NULL) {
+ /*
+ * recv_cb has been cleared - there is
+ * nothing to do
+ */
+ return (ISC_R_CANCELED);
+ } else if (sock->statichandle == NULL &&
+ atomic_load(&sock->connected) &&
+ !atomic_load(&sock->connecting))
+ {
+ /*
+ * It seems that some unexpected data (a DNS message) has
+ * arrived while we are wrapping up.
+ */
+ return (ISC_R_CANCELED);
+ }
+
+ req = isc__nm_get_read_req(sock, NULL);
+ REQUIRE(VALID_UVREQ(req));
+
+ /*
+ * We need to launch isc__nm_resume_processing() after the buffer
+ * has been consumed, thus we must delay detaching the handle.
+ */
+ isc_nmhandle_attach(req->handle, &handle);
+
+ /*
+ * The callback will be called synchronously because the
+ * result is ISC_R_SUCCESS, so we don't need to have
+ * the buffer on the heap
+ */
+ req->uvbuf.base = (char *)sock->buf + 2;
+ req->uvbuf.len = len;
+
+ /*
+ * If isc__nm_tcpdns_read() was called, it will be satisfied by single
+ * DNS message in the next call.
+ */
+ sock->recv_read = false;
+
+ /*
+ * An assertion failure here means that there's an erroneous
+ * extra nmhandle detach happening in the callback and
+ * isc__nm_resume_processing() is called while we're
+ * processing the buffer.
+ */
+ REQUIRE(sock->processing == false);
+ sock->processing = true;
+ isc__nm_readcb(sock, req, ISC_R_SUCCESS);
+ sock->processing = false;
+
+ len += 2;
+ sock->buf_len -= len;
+ if (sock->buf_len > 0) {
+ memmove(sock->buf, sock->buf + len, sock->buf_len);
+ }
+
+ isc_nmhandle_detach(&handle);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__nm_tcpdns_read_cb(uv_stream_t *stream, ssize_t nread,
+ const uv_buf_t *buf) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
+ uint8_t *base = NULL;
+ size_t len;
+ isc_result_t result;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->reading));
+ REQUIRE(buf != NULL);
+
+ if (isc__nmsocket_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, true);
+ goto free;
+ }
+
+ if (nread < 0) {
+ if (nread != UV_EOF) {
+ isc__nm_incstats(sock, STATID_RECVFAIL);
+ }
+
+ isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread), true);
+ goto free;
+ }
+
+ base = (uint8_t *)buf->base;
+ len = nread;
+
+ /*
+ * FIXME: We can avoid the memmove here if we know we have received full
+ * packet; e.g. we should be smarter, a.s. there are just few situations
+ *
+ * The tcp_alloc_buf should be smarter and point the uv_read_start to
+ * the position where previous read has ended in the sock->buf, that way
+ * the data could be read directly into sock->buf.
+ */
+
+ if (sock->buf_len + len > sock->buf_size) {
+ isc__nm_alloc_dnsbuf(sock, sock->buf_len + len);
+ }
+ memmove(sock->buf + sock->buf_len, base, len);
+ sock->buf_len += len;
+
+ if (!atomic_load(&sock->client)) {
+ sock->read_timeout = atomic_load(&sock->mgr->idle);
+ }
+
+ result = isc__nm_process_sock_buffer(sock);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_failed_read_cb(sock, result, true);
+ }
+free:
+ if (nread < 0) {
+ /*
+ * The buffer may be a null buffer on error.
+ */
+ if (buf->base == NULL && buf->len == 0) {
+ return;
+ }
+ }
+
+ isc__nm_free_uvbuf(sock, buf);
+}
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0) {
+ isc_nmsocket_t *sock = (isc_nmsocket_t *)sock0;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ /*
+ * Create a tcpdnsaccept event and pass it using the async channel.
+ */
+
+ isc__netievent_tcpdnsaccept_t *ievent =
+ isc__nm_get_netievent_tcpdnsaccept(sock->mgr, sock, quota);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+/*
+ * This is called after we get a quota_accept_cb() callback.
+ */
+void
+isc__nm_async_tcpdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnsaccept_t *ievent =
+ (isc__netievent_tcpdnsaccept_t *)ev0;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ result = accept_connection(ievent->sock, ievent->quota);
+ isc__nm_accept_connection_log(result, can_log_tcpdns_quota());
+}
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
+ isc_nmsocket_t *csock = NULL;
+ isc__networker_t *worker = NULL;
+ int r;
+ isc_result_t result;
+ struct sockaddr_storage peer_ss;
+ struct sockaddr_storage local_ss;
+ isc_sockaddr_t local;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ if (quota != NULL) {
+ isc_quota_detach(&quota);
+ }
+ return (ISC_R_CANCELED);
+ }
+
+ REQUIRE(ssock->accept_cb != NULL);
+
+ csock = isc_mem_get(ssock->mgr->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(csock, ssock->mgr, isc_nm_tcpdnssocket,
+ &ssock->iface);
+ csock->tid = ssock->tid;
+ csock->extrahandlesize = ssock->extrahandlesize;
+ isc__nmsocket_attach(ssock, &csock->server);
+ csock->recv_cb = ssock->recv_cb;
+ csock->recv_cbarg = ssock->recv_cbarg;
+ csock->quota = quota;
+ atomic_init(&csock->accepting, true);
+
+ worker = &csock->mgr->workers[csock->tid];
+
+ r = uv_tcp_init(&worker->loop, &csock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&csock->uv_handle.handle, csock);
+
+ r = uv_timer_init(&worker->loop, &csock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&csock->read_timer, csock);
+
+ r = uv_accept(&ssock->uv_handle.stream, &csock->uv_handle.stream);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ r = uv_tcp_getpeername(&csock->uv_handle.tcp,
+ (struct sockaddr *)&peer_ss,
+ &(int){ sizeof(peer_ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&csock->peer,
+ (struct sockaddr *)&peer_ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ r = uv_tcp_getsockname(&csock->uv_handle.tcp,
+ (struct sockaddr *)&local_ss,
+ &(int){ sizeof(local_ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&local,
+ (struct sockaddr *)&local_ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * The handle will be either detached on acceptcb failure or in the
+ * readcb.
+ */
+ handle = isc__nmhandle_get(csock, NULL, &local);
+
+ result = ssock->accept_cb(handle, ISC_R_SUCCESS, ssock->accept_cbarg);
+ if (result != ISC_R_SUCCESS) {
+ isc_nmhandle_detach(&handle);
+ goto failure;
+ }
+
+ atomic_store(&csock->accepting, false);
+
+ isc__nm_incstats(csock, STATID_ACCEPT);
+
+ csock->read_timeout = atomic_load(&csock->mgr->init);
+
+ csock->closehandle_cb = isc__nm_resume_processing;
+
+ /*
+ * We need to keep the handle alive until we fail to read or connection
+ * is closed by the other side, it will be detached via
+ * prep_destroy()->tcpdns_close_direct().
+ */
+ isc_nmhandle_attach(handle, &csock->recv_handle);
+ result = isc__nm_process_sock_buffer(csock);
+ if (result != ISC_R_SUCCESS) {
+ isc_nmhandle_detach(&csock->recv_handle);
+ isc_nmhandle_detach(&handle);
+ goto failure;
+ }
+
+ /*
+ * The initial timer has been set, update the read timeout for the next
+ * reads.
+ */
+ csock->read_timeout = (atomic_load(&csock->keepalive)
+ ? atomic_load(&csock->mgr->keepalive)
+ : atomic_load(&csock->mgr->idle));
+
+ isc_nmhandle_detach(&handle);
+
+ /*
+ * sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&csock);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+
+ atomic_store(&csock->active, false);
+
+ isc__nm_failed_accept_cb(csock, result);
+
+ isc__nmsocket_prep_destroy(csock);
+
+ isc__nmsocket_detach(&csock);
+
+ return (result);
+}
+
+void
+isc__nm_tcpdns_send(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc__netievent_tcpdnssend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+
+ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+ *(uint16_t *)uvreq->tcplen = htons(region->length);
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ isc_nmhandle_attach(handle, &uvreq->handle);
+
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+
+ ievent = isc__nm_get_netievent_tcpdnssend(sock->mgr, sock, uvreq);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+static void
+tcpdns_send_cb(uv_write_t *req, int status) {
+ isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMSOCK(uvreq->sock));
+
+ sock = uvreq->sock;
+
+ isc_nm_timer_stop(uvreq->timer);
+ isc_nm_timer_detach(&uvreq->timer);
+
+ if (status < 0) {
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq,
+ isc__nm_uverr2result(status));
+ return;
+ }
+
+ isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, false);
+}
+
+/*
+ * Handle 'tcpsend' async event - send a packet on the socket
+ */
+void
+isc__nm_async_tcpdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc_result_t result;
+ isc__netievent_tcpdnssend_t *ievent =
+ (isc__netievent_tcpdnssend_t *)ev0;
+ isc_nmsocket_t *sock = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+ int r, nbufs = 2;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_UVREQ(ievent->req));
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ sock = ievent->sock;
+ uvreq = ievent->req;
+
+ if (sock->write_timeout == 0) {
+ sock->write_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ uv_buf_t bufs[2] = { { .base = uvreq->tcplen, .len = 2 },
+ { .base = uvreq->uvbuf.base,
+ .len = uvreq->uvbuf.len } };
+
+ if (isc__nmsocket_closing(sock)) {
+ result = ISC_R_CANCELED;
+ goto fail;
+ }
+
+ r = uv_try_write(&sock->uv_handle.stream, bufs, nbufs);
+
+ if (r == (int)(bufs[0].len + bufs[1].len)) {
+ /* Wrote everything */
+ isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, true);
+ return;
+ }
+
+ if (r == 1) {
+ /* Partial write of DNSMSG length */
+ bufs[0].base = uvreq->tcplen + 1;
+ bufs[0].len = 1;
+ } else if (r > 0) {
+ /* Partial write of DNSMSG */
+ nbufs = 1;
+ bufs[0].base = uvreq->uvbuf.base + (r - 2);
+ bufs[0].len = uvreq->uvbuf.len - (r - 2);
+ } else if (r == UV_ENOSYS || r == UV_EAGAIN) {
+ /* uv_try_write not supported, send asynchronously */
+ } else {
+ /* error sending data */
+ result = isc__nm_uverr2result(r);
+ goto fail;
+ }
+
+ r = uv_write(&uvreq->uv_req.write, &sock->uv_handle.stream, bufs, nbufs,
+ tcpdns_send_cb);
+ if (r < 0) {
+ result = isc__nm_uverr2result(r);
+ goto fail;
+ }
+
+ isc_nm_timer_create(uvreq->handle, isc__nmsocket_writetimeout_cb, uvreq,
+ &uvreq->timer);
+ if (sock->write_timeout > 0) {
+ isc_nm_timer_start(uvreq->timer, sock->write_timeout);
+ }
+
+ return;
+fail:
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq, result);
+}
+
+static void
+tcpdns_stop_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ uv_handle_set_data(handle, NULL);
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ atomic_store(&sock->listening, false);
+
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tcpdns_close_sock(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ if (sock->server != NULL) {
+ isc__nmsocket_detach(&sock->server);
+ }
+
+ atomic_store(&sock->connected, false);
+
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tcpdns_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ uv_handle_set_data(handle, NULL);
+
+ tcpdns_close_sock(sock);
+}
+
+static void
+read_timer_close_cb(uv_handle_t *timer) {
+ isc_nmsocket_t *sock = uv_handle_get_data(timer);
+ uv_handle_set_data(timer, NULL);
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (sock->parent) {
+ uv_close(&sock->uv_handle.handle, tcpdns_stop_cb);
+ } else if (uv_is_closing(&sock->uv_handle.handle)) {
+ tcpdns_close_sock(sock);
+ } else {
+ uv_close(&sock->uv_handle.handle, tcpdns_close_cb);
+ }
+}
+
+static void
+stop_tcpdns_child(isc_nmsocket_t *sock) {
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ tcpdns_close_direct(sock);
+
+ atomic_fetch_sub(&sock->parent->rchildren, 1);
+
+ isc_barrier_wait(&sock->parent->stoplistening);
+}
+
+static void
+stop_tcpdns_parent(isc_nmsocket_t *sock) {
+ isc_nmsocket_t *csock = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tcpdnslistener);
+
+ isc_barrier_init(&sock->stoplistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ csock = &sock->children[i];
+ REQUIRE(VALID_NMSOCK(csock));
+
+ if ((int)i == isc_nm_tid()) {
+ /*
+ * We need to schedule closing the other sockets first
+ */
+ continue;
+ }
+
+ atomic_store(&csock->active, false);
+ enqueue_stoplistening(csock);
+ }
+
+ csock = &sock->children[isc_nm_tid()];
+ atomic_store(&csock->active, false);
+ stop_tcpdns_child(csock);
+
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tcpdns_close_direct(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ if (sock->recv_handle != NULL) {
+ isc_nmhandle_detach(&sock->recv_handle);
+ }
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+ uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb);
+}
+
+void
+isc__nm_tcpdns_close(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ if (sock->tid == isc_nm_tid()) {
+ tcpdns_close_direct(sock);
+ } else {
+ /*
+ * We need to create an event and pass it using async channel
+ */
+ isc__netievent_tcpdnsclose_t *ievent =
+ isc__nm_get_netievent_tcpdnsclose(sock->mgr, sock);
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_tcpdnsclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnsclose_t *ievent =
+ (isc__netievent_tcpdnsclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ tcpdns_close_direct(sock);
+}
+
+static void
+tcpdns_close_connect_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ isc__nmsocket_prep_destroy(sock);
+ isc__nmsocket_detach(&sock);
+}
+
+void
+isc__nm_tcpdns_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+
+ /*
+ * If the socket is active, mark it inactive and
+ * continue. If it isn't active, stop now.
+ */
+ if (!isc__nmsocket_deactivate(sock)) {
+ return;
+ }
+
+ if (atomic_load(&sock->accepting)) {
+ return;
+ }
+
+ if (atomic_load(&sock->connecting)) {
+ isc_nmsocket_t *tsock = NULL;
+ isc__nmsocket_attach(sock, &tsock);
+ uv_close(&sock->uv_handle.handle, tcpdns_close_connect_cb);
+ return;
+ }
+
+ if (sock->statichandle != NULL) {
+ if (isc__nm_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false);
+ } else {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ }
+ return;
+ }
+
+ /*
+ * Otherwise, we just send the socket to abyss...
+ */
+ if (sock->parent == NULL) {
+ isc__nmsocket_prep_destroy(sock);
+ }
+}
+
+void
+isc__nm_tcpdns_cancelread(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tcpdnscancel_t *ievent = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tcpdnssocket);
+
+ ievent = isc__nm_get_netievent_tcpdnscancel(sock->mgr, sock, handle);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tcpdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tcpdnscancel_t *ievent =
+ (isc__netievent_tcpdnscancel_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ isc__nm_failed_read_cb(sock, ISC_R_EOF, false);
+}
diff --git a/lib/isc/netmgr/timer.c b/lib/isc/netmgr/timer.c
new file mode 100644
index 0000000..8328775
--- /dev/null
+++ b/lib/isc/netmgr/timer.c
@@ -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.
+ */
+
+#include <uv.h>
+
+#include <isc/netmgr.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+
+struct isc_nm_timer {
+ isc_refcount_t references;
+ uv_timer_t timer;
+ isc_nmhandle_t *handle;
+ isc_nm_timer_cb cb;
+ void *cbarg;
+};
+
+void
+isc_nm_timer_create(isc_nmhandle_t *handle, isc_nm_timer_cb cb, void *cbarg,
+ isc_nm_timer_t **timerp) {
+ isc__networker_t *worker = NULL;
+ isc_nmsocket_t *sock = NULL;
+ isc_nm_timer_t *timer = NULL;
+ int r;
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+ worker = &sock->mgr->workers[isc_nm_tid()];
+
+ /* TODO: per-loop object cache */
+ timer = isc_mem_get(sock->mgr->mctx, sizeof(*timer));
+ *timer = (isc_nm_timer_t){ .cb = cb, .cbarg = cbarg };
+ isc_refcount_init(&timer->references, 1);
+ isc_nmhandle_attach(handle, &timer->handle);
+
+ r = uv_timer_init(&worker->loop, &timer->timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+
+ uv_handle_set_data((uv_handle_t *)&timer->timer, timer);
+
+ *timerp = timer;
+}
+
+void
+isc_nm_timer_attach(isc_nm_timer_t *timer, isc_nm_timer_t **timerp) {
+ REQUIRE(timer != NULL);
+ REQUIRE(timerp != NULL && *timerp == NULL);
+
+ isc_refcount_increment(&timer->references);
+ *timerp = timer;
+}
+
+static void
+timer_destroy(uv_handle_t *uvhandle) {
+ isc_nm_timer_t *timer = uv_handle_get_data(uvhandle);
+ isc_nmhandle_t *handle = timer->handle;
+ isc_mem_t *mctx = timer->handle->sock->mgr->mctx;
+
+ isc_mem_put(mctx, timer, sizeof(*timer));
+
+ isc_nmhandle_detach(&handle);
+}
+
+void
+isc_nm_timer_detach(isc_nm_timer_t **timerp) {
+ isc_nm_timer_t *timer = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(timerp != NULL && *timerp != NULL);
+
+ timer = *timerp;
+ *timerp = NULL;
+
+ handle = timer->handle;
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ if (isc_refcount_decrement(&timer->references) == 1) {
+ int r = uv_timer_stop(&timer->timer);
+ UV_RUNTIME_CHECK(uv_timer_stop, r);
+ uv_close((uv_handle_t *)&timer->timer, timer_destroy);
+ }
+}
+
+static void
+timer_cb(uv_timer_t *uvtimer) {
+ isc_nm_timer_t *timer = uv_handle_get_data((uv_handle_t *)uvtimer);
+
+ REQUIRE(timer->cb != NULL);
+
+ timer->cb(timer->cbarg, ISC_R_TIMEDOUT);
+}
+
+void
+isc_nm_timer_start(isc_nm_timer_t *timer, uint64_t timeout) {
+ int r = uv_timer_start(&timer->timer, timer_cb, timeout, 0);
+ UV_RUNTIME_CHECK(uv_timer_start, r);
+}
+
+void
+isc_nm_timer_stop(isc_nm_timer_t *timer) {
+ int r = uv_timer_stop(&timer->timer);
+ UV_RUNTIME_CHECK(uv_timer_stop, r);
+}
diff --git a/lib/isc/netmgr/tlsdns.c b/lib/isc/netmgr/tlsdns.c
new file mode 100644
index 0000000..d30e33f
--- /dev/null
+++ b/lib/isc/netmgr/tlsdns.c
@@ -0,0 +1,2363 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <libgen.h>
+#include <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/errno.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stdtime.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+#include "openssl_shim.h"
+#include "uv-compat.h"
+
+static atomic_uint_fast32_t last_tlsdnsquota_log = 0;
+
+static void
+tls_error(isc_nmsocket_t *sock, isc_result_t result);
+
+static isc_result_t
+tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
+
+static void
+tlsdns_close_direct(isc_nmsocket_t *sock);
+
+static isc_result_t
+tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req);
+static void
+tlsdns_connect_cb(uv_connect_t *uvreq, int status);
+
+static void
+tlsdns_connection_cb(uv_stream_t *server, int status);
+
+static void
+tlsdns_close_cb(uv_handle_t *uvhandle);
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota);
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0);
+
+static void
+stop_tlsdns_parent(isc_nmsocket_t *sock);
+static void
+stop_tlsdns_child(isc_nmsocket_t *sock);
+
+static void
+async_tlsdns_cycle(isc_nmsocket_t *sock) __attribute__((unused));
+
+static isc_result_t
+tls_cycle(isc_nmsocket_t *sock);
+
+static void
+call_pending_send_callbacks(isc_nmsocket_t *sock, const isc_result_t result);
+
+static void
+tlsdns_keep_client_tls_session(isc_nmsocket_t *sock);
+
+static void
+tlsdns_set_tls_shutdown(isc_tls_t *tls) {
+ (void)SSL_set_shutdown(tls, SSL_SENT_SHUTDOWN);
+}
+
+static bool
+peer_verification_has_failed(isc_nmsocket_t *sock) {
+ if (sock->tls.tls != NULL && sock->tls.state == TLS_STATE_HANDSHAKE &&
+ SSL_get_verify_result(sock->tls.tls) != X509_V_OK)
+ {
+ return (true);
+ }
+
+ return (false);
+}
+
+static bool
+can_log_tlsdns_quota(void) {
+ isc_stdtime_t now, last;
+
+ isc_stdtime_get(&now);
+ last = atomic_exchange_relaxed(&last_tlsdnsquota_log, now);
+ if (now != last) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+tlsdns_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc__networker_t *worker = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ worker = &sock->mgr->workers[sock->tid];
+
+ atomic_store(&sock->connecting, true);
+
+ /* 2 minute timeout */
+ result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ }
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r != 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (req->local.length != 0) {
+ r = uv_tcp_bind(&sock->uv_handle.tcp, &req->local.type.sa, 0);
+ /*
+ * In case of shared socket UV_EINVAL will be returned and needs
+ * to be ignored
+ */
+ if (r != 0 && r != UV_EINVAL) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ uv_handle_set_data(&req->uv_req.handle, req);
+ r = uv_tcp_connect(&req->uv_req.connect, &sock->uv_handle.tcp,
+ &req->peer.type.sa, tlsdns_connect_cb);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_CONNECTFAIL);
+ goto done;
+ }
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer,
+ &req->uv_req.connect);
+ isc__nmsocket_timer_start(sock);
+
+ atomic_store(&sock->connected, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+error:
+ LOCK(&sock->lock);
+ sock->result = result;
+ SIGNAL(&sock->cond);
+ if (!atomic_load(&sock->active)) {
+ WAIT(&sock->scond, &sock->lock);
+ }
+ INSIST(atomic_load(&sock->active));
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+void
+isc__nm_async_tlsdnsconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsconnect_t *ievent =
+ (isc__netievent_tlsdnsconnect_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(sock->parent == NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = tlsdns_connect_direct(sock, req);
+ if (result != ISC_R_SUCCESS) {
+ atomic_compare_exchange_enforced(&sock->connecting,
+ &(bool){ true }, false);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->active, false);
+ isc__nm_tlsdns_close(sock);
+ }
+
+ /*
+ * The sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tlsdns_connect_cb(uv_connect_t *uvreq, int status) {
+ isc_result_t result = ISC_R_UNSET;
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)uvreq->handle);
+ struct sockaddr_storage ss;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ req = uv_handle_get_data((uv_handle_t *)uvreq);
+
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(VALID_NMHANDLE(req->handle));
+
+ if (atomic_load(&sock->timedout)) {
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (isc__nm_closing(sock)) {
+ /* Network manager shutting down */
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ } else if (isc__nmsocket_closing(sock)) {
+ /* Connection canceled */
+ result = ISC_R_CANCELED;
+ goto error;
+ } else if (status == UV_ETIMEDOUT) {
+ /* Timeout status code here indicates hard error */
+ result = ISC_R_TIMEDOUT;
+ goto error;
+ } else if (status == UV_EADDRINUSE) {
+ /*
+ * On FreeBSD the TCP connect() call sometimes results in a
+ * spurious transient EADDRINUSE. Try a few more times before
+ * giving up.
+ */
+ if (--req->connect_tries > 0) {
+ r = uv_tcp_connect(
+ &req->uv_req.connect, &sock->uv_handle.tcp,
+ &req->peer.type.sa, tlsdns_connect_cb);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+ return;
+ }
+ result = isc__nm_uverr2result(status);
+ goto error;
+ } else if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto error;
+ }
+
+ isc__nm_incstats(sock, STATID_CONNECT);
+ r = uv_tcp_getpeername(&sock->uv_handle.tcp, (struct sockaddr *)&ss,
+ &(int){ sizeof(ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto error;
+ }
+
+ sock->tls.state = TLS_STATE_NONE;
+ sock->tls.tls = isc_tls_create(sock->tls.ctx);
+ RUNTIME_CHECK(sock->tls.tls != NULL);
+
+ /*
+ *
+ */
+ r = BIO_new_bio_pair(&sock->tls.ssl_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE,
+ &sock->tls.app_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE);
+ RUNTIME_CHECK(r == 1);
+
+ r = BIO_new_bio_pair(&sock->tls.ssl_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE,
+ &sock->tls.app_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE);
+ RUNTIME_CHECK(r == 1);
+
+#if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
+ /*
+ * Note that if the rbio and wbio are the same then
+ * SSL_set0_rbio() and SSL_set0_wbio() each take ownership of
+ * one reference. Therefore it may be necessary to increment the
+ * number of references available using BIO_up_ref(3) before
+ * calling the set0 functions.
+ */
+ SSL_set0_rbio(sock->tls.tls, sock->tls.ssl_rbio);
+ SSL_set0_wbio(sock->tls.tls, sock->tls.ssl_wbio);
+#else
+ SSL_set_bio(sock->tls.tls, sock->tls.ssl_rbio, sock->tls.ssl_wbio);
+#endif
+
+ result = isc_sockaddr_fromsockaddr(&sock->peer, (struct sockaddr *)&ss);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (sock->tls.client_sess_cache != NULL) {
+ isc_tlsctx_client_session_cache_reuse_sockaddr(
+ sock->tls.client_sess_cache, &sock->peer,
+ sock->tls.tls);
+ }
+
+ SSL_set_connect_state(sock->tls.tls);
+
+ /* Setting pending req */
+ sock->tls.pending_req = req;
+
+ result = isc__nm_process_sock_buffer(sock);
+ if (result != ISC_R_SUCCESS) {
+ sock->tls.pending_req = NULL;
+ goto error;
+ }
+
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ sock->tls.pending_req = NULL;
+ goto error;
+ }
+
+ return;
+
+error:
+ isc__nm_failed_connect_cb(sock, req, result, false);
+}
+
+void
+isc_nm_tlsdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize, isc_tlsctx_t *sslctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tlsdnsconnect_t *ievent = NULL;
+ isc__nm_uvreq_t *req = NULL;
+ sa_family_t sa_family;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(local != NULL);
+ REQUIRE(peer != NULL);
+ REQUIRE(sslctx != NULL);
+
+ sa_family = peer->type.sa.sa_family;
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tlsdnssocket, local);
+
+ sock->extrahandlesize = extrahandlesize;
+ sock->connect_timeout = timeout;
+ sock->result = ISC_R_UNSET;
+ isc_tlsctx_attach(sslctx, &sock->tls.ctx);
+ atomic_init(&sock->client, true);
+ atomic_init(&sock->connecting, true);
+
+ req = isc__nm_uvreq_get(mgr, sock);
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->peer = *peer;
+ req->local = *local;
+ req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface);
+
+ if (client_sess_cache != NULL) {
+ INSIST(isc_tlsctx_client_session_cache_getctx(
+ client_sess_cache) == sslctx);
+ isc_tlsctx_client_session_cache_attach(
+ client_sess_cache, &sock->tls.client_sess_cache);
+ }
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto failure;
+ }
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ /* 2 minute timeout */
+ result = isc__nm_socket_connectiontimeout(sock->fd, 120 * 1000);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ ievent = isc__nm_get_netievent_tlsdnsconnect(mgr, sock, req);
+
+ if (isc__nm_in_netthread()) {
+ atomic_store(&sock->active, true);
+ sock->tid = isc_nm_tid();
+ isc__nm_async_tlsdnsconnect(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ isc__nm_put_netievent_tlsdnsconnect(mgr, ievent);
+ } else {
+ atomic_init(&sock->active, false);
+ sock->tid = isc_random_uniform(mgr->nworkers);
+ isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+ LOCK(&sock->lock);
+ while (sock->result == ISC_R_UNSET) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ atomic_store(&sock->active, true);
+ BROADCAST(&sock->scond);
+ UNLOCK(&sock->lock);
+ return;
+
+failure:
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ }
+
+ atomic_compare_exchange_enforced(&sock->connecting, &(bool){ true },
+ false);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+}
+
+static uv_os_sock_t
+isc__nm_tlsdns_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) {
+ isc_result_t result;
+ uv_os_sock_t sock;
+
+ result = isc__nm_socket(sa_family, SOCK_STREAM, 0, &sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ (void)isc__nm_socket_incoming_cpu(sock);
+ (void)isc__nm_socket_v6only(sock, sa_family);
+
+ /* FIXME: set mss */
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ return (sock);
+}
+
+static void
+start_tlsdns_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock,
+ uv_os_sock_t fd, int tid) {
+ isc__netievent_tlsdnslisten_t *ievent = NULL;
+ isc_nmsocket_t *csock = &sock->children[tid];
+
+ isc__nmsocket_init(csock, mgr, isc_nm_tlsdnssocket, iface);
+ csock->parent = sock;
+ csock->accept_cb = sock->accept_cb;
+ csock->accept_cbarg = sock->accept_cbarg;
+ csock->recv_cb = sock->recv_cb;
+ csock->recv_cbarg = sock->recv_cbarg;
+ csock->extrahandlesize = sock->extrahandlesize;
+ csock->backlog = sock->backlog;
+ csock->tid = tid;
+ isc_tlsctx_attach(sock->tls.ctx, &csock->tls.ctx);
+
+ /*
+ * We don't attach to quota, just assign - to avoid
+ * increasing quota unnecessarily.
+ */
+ csock->pquota = sock->pquota;
+ isc_quota_cb_init(&csock->quotacb, quota_accept_cb, csock);
+
+ if (mgr->load_balance_sockets) {
+ UNUSED(fd);
+ csock->fd = isc__nm_tlsdns_lb_socket(mgr,
+ iface->type.sa.sa_family);
+ } else {
+ csock->fd = dup(fd);
+ }
+ REQUIRE(csock->fd >= 0);
+
+ ievent = isc__nm_get_netievent_tlsdnslisten(mgr, csock);
+ isc__nm_maybe_enqueue_ievent(&mgr->workers[tid],
+ (isc__netievent_t *)ievent);
+}
+
+static void
+enqueue_stoplistening(isc_nmsocket_t *sock) {
+ isc__netievent_tlsdnsstop_t *ievent =
+ isc__nm_get_netievent_tlsdnsstop(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+isc_result_t
+isc_nm_listentlsdns(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_recv_cb_t recv_cb, void *recv_cbarg,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ isc_tlsctx_t *sslctx, isc_nmsocket_t **sockp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ size_t children_size = 0;
+ uv_os_sock_t fd = -1;
+
+ REQUIRE(VALID_NM(mgr));
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_tlsdnslistener, iface);
+
+ atomic_init(&sock->rchildren, 0);
+ sock->nchildren = mgr->nworkers;
+ children_size = sock->nchildren * sizeof(sock->children[0]);
+ sock->children = isc_mem_get(mgr->mctx, children_size);
+ memset(sock->children, 0, children_size);
+
+ sock->result = ISC_R_UNSET;
+ sock->accept_cb = accept_cb;
+ sock->accept_cbarg = accept_cbarg;
+ sock->recv_cb = recv_cb;
+ sock->recv_cbarg = recv_cbarg;
+ sock->extrahandlesize = extrahandlesize;
+ sock->backlog = backlog;
+ sock->pquota = quota;
+
+ isc_tlsctx_attach(sslctx, &sock->tls.ctx);
+
+ sock->tid = 0;
+ sock->fd = -1;
+
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_tlsdns_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+
+ isc_barrier_init(&sock->startlistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ if ((int)i == isc_nm_tid()) {
+ continue;
+ }
+ start_tlsdns_child(mgr, iface, sock, fd, i);
+ }
+
+ if (isc__nm_in_netthread()) {
+ start_tlsdns_child(mgr, iface, sock, fd, isc_nm_tid());
+ }
+
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+
+ LOCK(&sock->lock);
+ while (atomic_load(&sock->rchildren) != sock->nchildren) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ result = sock->result;
+ atomic_store(&sock->active, true);
+ UNLOCK(&sock->lock);
+
+ INSIST(result != ISC_R_UNSET);
+
+ if (result == ISC_R_SUCCESS) {
+ REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren);
+ *sockp = sock;
+ } else {
+ atomic_store(&sock->active, false);
+ enqueue_stoplistening(sock);
+ isc_nmsocket_close(&sock);
+ }
+
+ return (result);
+}
+
+void
+isc__nm_async_tlsdnslisten(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnslisten_t *ievent =
+ (isc__netievent_tlsdnslisten_t *)ev0;
+ sa_family_t sa_family;
+ int r;
+ int flags = 0;
+ isc_nmsocket_t *sock = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ isc_nm_t *mgr;
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+ REQUIRE(VALID_NMSOCK(ievent->sock->parent));
+
+ sock = ievent->sock;
+ sa_family = sock->iface.type.sa.sa_family;
+ mgr = sock->mgr;
+
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(sock->parent != NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+ (void)isc__nm_socket_tcp_maxseg(sock->fd, NM_MAXSEG);
+
+ r = uv_tcp_init(&worker->loop, &sock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+ /* This keeps the socket alive after everything else is gone */
+ isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL });
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ LOCK(&sock->parent->lock);
+
+ r = uv_tcp_open(&sock->uv_handle.tcp, sock->fd);
+ if (r < 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (sa_family == AF_INET6) {
+ flags = UV_TCP_IPV6ONLY;
+ }
+
+ if (mgr->load_balance_sockets) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ } else {
+ if (sock->parent->fd == -1) {
+ r = isc_uv_tcp_freebind(&sock->uv_handle.tcp,
+ &sock->iface.type.sa, flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ sock->parent->uv_handle.tcp.flags =
+ sock->uv_handle.tcp.flags;
+ sock->parent->fd = sock->fd;
+ } else {
+ /* The socket is already bound, just copy the flags */
+ sock->uv_handle.tcp.flags =
+ sock->parent->uv_handle.tcp.flags;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ /*
+ * The callback will run in the same thread uv_listen() was
+ * called from, so a race with tlsdns_connection_cb() isn't
+ * possible.
+ */
+ r = uv_listen((uv_stream_t *)&sock->uv_handle.tcp, sock->backlog,
+ tlsdns_connection_cb);
+ if (r != 0) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_NETMGR, ISC_LOG_ERROR,
+ "uv_listen failed: %s",
+ isc_result_totext(isc__nm_uverr2result(r)));
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+
+ atomic_store(&sock->listening, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+ if (result != ISC_R_SUCCESS) {
+ sock->pquota = NULL;
+ }
+
+ atomic_fetch_add(&sock->parent->rchildren, 1);
+ if (sock->parent->result == ISC_R_UNSET) {
+ sock->parent->result = result;
+ }
+ SIGNAL(&sock->parent->cond);
+ UNLOCK(&sock->parent->lock);
+
+ isc_barrier_wait(&sock->parent->startlistening);
+}
+
+static void
+tlsdns_connection_cb(uv_stream_t *server, int status) {
+ isc_nmsocket_t *ssock = uv_handle_get_data((uv_handle_t *)server);
+ isc_result_t result;
+ isc_quota_t *quota = NULL;
+
+ if (status != 0) {
+ result = isc__nm_uverr2result(status);
+ goto done;
+ }
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ result = ISC_R_CANCELED;
+ goto done;
+ }
+
+ if (ssock->pquota != NULL) {
+ result = isc_quota_attach_cb(ssock->pquota, &quota,
+ &ssock->quotacb);
+ if (result == ISC_R_QUOTA) {
+ isc__nm_incstats(ssock, STATID_ACCEPTFAIL);
+ goto done;
+ }
+ }
+
+ result = accept_connection(ssock, quota);
+done:
+ isc__nm_accept_connection_log(result, can_log_tlsdns_quota());
+}
+
+void
+isc__nm_tlsdns_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnslistener);
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ if (!isc__nm_in_netthread()) {
+ enqueue_stoplistening(sock);
+ } else {
+ stop_tlsdns_parent(sock);
+ }
+}
+
+static void
+tls_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+
+ isc__netievent_tlsdnsshutdown_t *ievent =
+ isc__nm_get_netievent_tlsdnsshutdown(sock->mgr, sock);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tlsdnsshutdown(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsshutdown_t *ievent =
+ (isc__netievent_tlsdnsshutdown_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ int rv;
+ int err;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+
+ if (sock->tls.state != TLS_STATE_IO) {
+ /* Nothing to do */
+ return;
+ }
+
+ rv = SSL_shutdown(sock->tls.tls);
+
+ if (rv == 1) {
+ sock->tls.state = TLS_STATE_NONE;
+ /* FIXME: continue closing the socket */
+ return;
+ }
+
+ if (rv == 0) {
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ tls_error(sock, result);
+ return;
+ }
+
+ /* Reschedule closing the socket */
+ tls_shutdown(sock);
+ return;
+ }
+
+ err = SSL_get_error(sock->tls.tls, rv);
+
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ tls_error(sock, result);
+ return;
+ }
+
+ /* Reschedule closing the socket */
+ tls_shutdown(sock);
+ return;
+ case 0:
+ UNREACHABLE();
+ case SSL_ERROR_ZERO_RETURN:
+ tls_error(sock, ISC_R_EOF);
+ break;
+ default:
+ tls_error(sock, ISC_R_TLSERROR);
+ }
+ return;
+}
+
+void
+isc__nm_async_tlsdnsstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsstop_t *ievent =
+ (isc__netievent_tlsdnsstop_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->parent != NULL) {
+ stop_tlsdns_child(sock);
+ return;
+ }
+
+ stop_tlsdns_parent(sock);
+}
+
+void
+isc__nm_tlsdns_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result,
+ bool async) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ if (sock->tls.pending_req != NULL) {
+ isc_result_t failure_result = ISC_R_CANCELED;
+ isc__nm_uvreq_t *req = sock->tls.pending_req;
+ sock->tls.pending_req = NULL;
+
+ if (peer_verification_has_failed(sock)) {
+ /*
+ * Save error message as 'sock->tls' will get detached.
+ */
+ sock->tls.tls_verify_errmsg =
+ isc_tls_verify_peer_result_string(
+ sock->tls.tls);
+ failure_result = ISC_R_TLSBADPEERCERT;
+ }
+ isc__nm_failed_connect_cb(sock, req, failure_result, async);
+ }
+
+ if (!sock->recv_read) {
+ goto destroy;
+ }
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_readcb(sock, req, result);
+ }
+
+destroy:
+ call_pending_send_callbacks(sock, result);
+ isc__nmsocket_prep_destroy(sock);
+
+ /*
+ * We need to detach from quota after the read callback function
+ * had a chance to be executed.
+ */
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+}
+
+void
+isc__nm_tlsdns_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+ isc__netievent_tlsdnsread_t *ievent = NULL;
+
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(sock->statichandle == handle);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+ if (sock->read_timeout == 0) {
+ sock->read_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ ievent = isc__nm_get_netievent_tlsdnsread(sock->mgr, sock);
+
+ /*
+ * This MUST be done asynchronously, no matter which thread
+ * we're in. The callback function for isc_nm_read() often calls
+ * isc_nm_read() again; if we tried to do that synchronously
+ * we'd clash in processbuffer() and grow the stack
+ * indefinitely.
+ */
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+
+ return;
+}
+
+void
+isc__nm_async_tlsdnsread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsread_t *ievent =
+ (isc__netievent_tlsdnsread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc_result_t result = ISC_R_SUCCESS;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(sock)) {
+ atomic_store(&sock->reading, true);
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ return;
+ }
+
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_failed_read_cb(sock, result, false);
+ }
+}
+
+/*
+ * Process a single packet from the incoming buffer.
+ *
+ * Return ISC_R_SUCCESS and attach 'handlep' to a handle if something
+ * was processed; return ISC_R_NOMORE if there isn't a full message
+ * to be processed.
+ *
+ * The caller will need to unreference the handle.
+ */
+isc_result_t
+isc__nm_tlsdns_processbuffer(isc_nmsocket_t *sock) {
+ size_t len;
+ isc__nm_uvreq_t *req = NULL;
+ isc_nmhandle_t *handle = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+ /*
+ * If we don't even have the length yet, we can't do
+ * anything.
+ */
+ if (sock->buf_len < 2) {
+ return (ISC_R_NOMORE);
+ }
+
+ /*
+ * Process the first packet from the buffer, leaving
+ * the rest (if any) for later.
+ */
+ len = ntohs(*(uint16_t *)sock->buf);
+ if (len > sock->buf_len - 2) {
+ return (ISC_R_NOMORE);
+ }
+
+ if (sock->recv_cb == NULL) {
+ /*
+ * recv_cb has been cleared - there is
+ * nothing to do
+ */
+ return (ISC_R_CANCELED);
+ } else if (sock->statichandle == NULL &&
+ sock->tls.state == TLS_STATE_IO &&
+ atomic_load(&sock->connected) &&
+ !atomic_load(&sock->connecting))
+ {
+ /*
+ * It seems that some unexpected data (a DNS message) has
+ * arrived while we are wrapping up.
+ */
+ return (ISC_R_CANCELED);
+ }
+
+ req = isc__nm_get_read_req(sock, NULL);
+ REQUIRE(VALID_UVREQ(req));
+
+ /*
+ * We need to launch isc__nm_resume_processing() after the buffer
+ * has been consumed, thus we must delay detaching the handle.
+ */
+ isc_nmhandle_attach(req->handle, &handle);
+
+ /*
+ * The callback will be called synchronously because the
+ * result is ISC_R_SUCCESS, so we don't need to have
+ * the buffer on the heap
+ */
+ req->uvbuf.base = (char *)sock->buf + 2;
+ req->uvbuf.len = len;
+
+ /*
+ * If isc__nm_tlsdns_read() was called, it will be satisfied by
+ * single DNS message in the next call.
+ */
+ sock->recv_read = false;
+
+ /*
+ * An assertion failure here means that there's an erroneous
+ * extra nmhandle detach happening in the callback and
+ * isc__nm_resume_processing() is called while we're
+ * processing the buffer.
+ */
+ REQUIRE(sock->processing == false);
+ sock->processing = true;
+ isc__nm_readcb(sock, req, ISC_R_SUCCESS);
+ sock->processing = false;
+
+ len += 2;
+ sock->buf_len -= len;
+ if (sock->buf_len > 0) {
+ memmove(sock->buf, sock->buf + len, sock->buf_len);
+ }
+
+ isc_nmhandle_detach(&handle);
+
+ if (isc__nmsocket_closing(sock)) {
+ tlsdns_set_tls_shutdown(sock->tls.tls);
+ tlsdns_keep_client_tls_session(sock);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+tls_cycle_input(isc_nmsocket_t *sock) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int err = 0;
+ int rv = 1;
+
+ if (sock->tls.state == TLS_STATE_IO) {
+ size_t len;
+
+ for (;;) {
+ (void)SSL_peek(sock->tls.tls, &(char){ '\0' }, 0);
+
+ int pending = SSL_pending(sock->tls.tls);
+ if (pending > (int)ISC_NETMGR_TCP_RECVBUF_SIZE) {
+ pending = (int)ISC_NETMGR_TCP_RECVBUF_SIZE;
+ }
+
+ if (pending != 0) {
+ if ((sock->buf_len + pending) > sock->buf_size)
+ {
+ isc__nm_alloc_dnsbuf(
+ sock, sock->buf_len + pending);
+ }
+
+ len = 0;
+ rv = SSL_read_ex(sock->tls.tls,
+ sock->buf + sock->buf_len,
+ sock->buf_size - sock->buf_len,
+ &len);
+ if (rv != 1) {
+ /*
+ * Process what's in the buffer so far
+ */
+ result = isc__nm_process_sock_buffer(
+ sock);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ /*
+ * FIXME: Should we call
+ * isc__nm_failed_read_cb()?
+ */
+ break;
+ }
+
+ INSIST((size_t)pending == len);
+
+ sock->buf_len += len;
+ }
+ result = isc__nm_process_sock_buffer(sock);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ if (pending == 0) {
+ break;
+ }
+ }
+ } else if (!SSL_is_init_finished(sock->tls.tls)) {
+ if (SSL_is_server(sock->tls.tls)) {
+ rv = SSL_accept(sock->tls.tls);
+ } else {
+ rv = SSL_connect(sock->tls.tls);
+ }
+
+ } else {
+ rv = 1;
+ }
+
+ if (rv <= 0) {
+ err = SSL_get_error(sock->tls.tls, rv);
+ }
+
+ switch (err) {
+ case SSL_ERROR_WANT_READ:
+ if (sock->tls.state == TLS_STATE_NONE &&
+ !SSL_is_init_finished(sock->tls.tls))
+ {
+ sock->tls.state = TLS_STATE_HANDSHAKE;
+ result = isc__nm_process_sock_buffer(sock);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ /* else continue reading */
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ async_tlsdns_cycle(sock);
+ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ /* Continue reading/writing */
+ break;
+ case 0:
+ /* Everything is ok, continue */
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ return (ISC_R_EOF);
+ default:
+ return (ISC_R_TLSERROR);
+ }
+
+ /* Stop state after handshake */
+ if (sock->tls.state == TLS_STATE_HANDSHAKE &&
+ SSL_is_init_finished(sock->tls.tls))
+ {
+ const unsigned char *alpn = NULL;
+ unsigned int alpnlen = 0;
+
+ isc__nmsocket_log_tls_session_reuse(sock, sock->tls.tls);
+
+ isc_tls_get_selected_alpn(sock->tls.tls, &alpn, &alpnlen);
+ if (alpn != NULL && alpnlen == ISC_TLS_DOT_PROTO_ALPN_ID_LEN &&
+ memcmp(ISC_TLS_DOT_PROTO_ALPN_ID, alpn,
+ ISC_TLS_DOT_PROTO_ALPN_ID_LEN) == 0)
+ {
+ sock->tls.alpn_negotiated = true;
+ }
+
+ sock->tls.state = TLS_STATE_IO;
+
+ if (SSL_is_server(sock->tls.tls)) {
+ REQUIRE(sock->recv_handle != NULL);
+ result = sock->accept_cb(sock->recv_handle,
+ ISC_R_SUCCESS,
+ sock->accept_cbarg);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_nmhandle_detach(&sock->recv_handle);
+ goto failure;
+ }
+ } else {
+ isc__nm_uvreq_t *req = sock->tls.pending_req;
+ sock->tls.pending_req = NULL;
+
+ isc__nmsocket_timer_stop(sock);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer,
+ sock);
+
+ atomic_compare_exchange_enforced(
+ &sock->connecting, &(bool){ true }, false);
+ isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true);
+ }
+ async_tlsdns_cycle(sock);
+ }
+failure:
+ return (result);
+}
+
+static void
+tls_error(isc_nmsocket_t *sock, isc_result_t result) {
+ switch (sock->tls.state) {
+ case TLS_STATE_HANDSHAKE:
+ case TLS_STATE_IO:
+ if (atomic_load(&sock->connecting)) {
+ isc__nm_uvreq_t *req = sock->tls.pending_req;
+ sock->tls.pending_req = NULL;
+
+ isc__nm_failed_connect_cb(sock, req, result, false);
+ } else {
+ isc__nm_tlsdns_failed_read_cb(sock, result, false);
+ }
+ break;
+ case TLS_STATE_ERROR:
+ return;
+ default:
+ break;
+ }
+
+ sock->tls.state = TLS_STATE_ERROR;
+ sock->tls.pending_error = result;
+
+ isc__nmsocket_shutdown(sock);
+}
+
+static void
+call_pending_send_callbacks(isc_nmsocket_t *sock, const isc_result_t result) {
+ isc__nm_uvreq_t *cbreq = ISC_LIST_HEAD(sock->tls.sendreqs);
+ while (cbreq != NULL) {
+ isc__nm_uvreq_t *next = ISC_LIST_NEXT(cbreq, link);
+ ISC_LIST_UNLINK(sock->tls.sendreqs, cbreq, link);
+ INSIST(sock == cbreq->handle->sock);
+ isc__nm_sendcb(sock, cbreq, result, false);
+ cbreq = next;
+ }
+}
+
+static void
+free_senddata(isc_nmsocket_t *sock, const isc_result_t result) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tls.senddata.base != NULL);
+ REQUIRE(sock->tls.senddata.length > 0);
+
+ isc_mem_put(sock->mgr->mctx, sock->tls.senddata.base,
+ sock->tls.senddata.length);
+ sock->tls.senddata.base = NULL;
+ sock->tls.senddata.length = 0;
+
+ call_pending_send_callbacks(sock, result);
+}
+
+static void
+tls_write_cb(uv_write_t *req, int status) {
+ isc_result_t result = status != 0 ? isc__nm_uverr2result(status)
+ : ISC_R_SUCCESS;
+ isc__nm_uvreq_t *uvreq = (isc__nm_uvreq_t *)req->data;
+ isc_nmsocket_t *sock = uvreq->sock;
+
+ isc_nm_timer_stop(uvreq->timer);
+ isc_nm_timer_detach(&uvreq->timer);
+
+ free_senddata(sock, result);
+
+ isc__nm_uvreq_put(&uvreq, sock);
+
+ if (status != 0) {
+ tls_error(sock, result);
+ return;
+ }
+
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ tls_error(sock, result);
+ return;
+ }
+}
+
+static isc_result_t
+tls_cycle_output(isc_nmsocket_t *sock) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int pending;
+
+ while ((pending = BIO_pending(sock->tls.app_rbio)) > 0) {
+ isc__nm_uvreq_t *req = NULL;
+ size_t bytes;
+ int rv;
+ int r;
+
+ if (sock->tls.senddata.base != NULL ||
+ sock->tls.senddata.length > 0)
+ {
+ break;
+ }
+
+ if (pending > (int)ISC_NETMGR_TCP_RECVBUF_SIZE) {
+ pending = (int)ISC_NETMGR_TCP_RECVBUF_SIZE;
+ }
+
+ sock->tls.senddata.base = isc_mem_get(sock->mgr->mctx, pending);
+ sock->tls.senddata.length = pending;
+
+ /* It's a bit misnomer here, but it does the right thing */
+ req = isc__nm_get_read_req(sock, NULL);
+ req->uvbuf.base = (char *)sock->tls.senddata.base;
+ req->uvbuf.len = sock->tls.senddata.length;
+
+ rv = BIO_read_ex(sock->tls.app_rbio, req->uvbuf.base,
+ req->uvbuf.len, &bytes);
+
+ RUNTIME_CHECK(rv == 1);
+ INSIST((size_t)pending == bytes);
+
+ r = uv_try_write(&sock->uv_handle.stream, &req->uvbuf, 1);
+
+ if (r == pending) {
+ /* Wrote everything, restart */
+ isc__nm_uvreq_put(&req, sock);
+ free_senddata(sock, ISC_R_SUCCESS);
+ continue;
+ }
+
+ if (r > 0) {
+ /* Partial write, send rest asynchronously */
+ memmove(req->uvbuf.base, req->uvbuf.base + r,
+ req->uvbuf.len - r);
+ req->uvbuf.len = req->uvbuf.len - r;
+ } else if (r == UV_ENOSYS || r == UV_EAGAIN) {
+ /* uv_try_write is not supported, send
+ * asynchronously */
+ } else {
+ result = isc__nm_uverr2result(r);
+ isc__nm_uvreq_put(&req, sock);
+ free_senddata(sock, result);
+ break;
+ }
+
+ r = uv_write(&req->uv_req.write, &sock->uv_handle.stream,
+ &req->uvbuf, 1, tls_write_cb);
+ if (r < 0) {
+ result = isc__nm_uverr2result(r);
+ isc__nm_uvreq_put(&req, sock);
+ free_senddata(sock, result);
+ break;
+ }
+
+ isc_nm_timer_create(req->handle, isc__nmsocket_writetimeout_cb,
+ req, &req->timer);
+ if (sock->write_timeout > 0) {
+ isc_nm_timer_start(req->timer, sock->write_timeout);
+ }
+
+ break;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+tls_pop_error(isc_nmsocket_t *sock) {
+ isc_result_t result;
+
+ if (sock->tls.state != TLS_STATE_ERROR) {
+ return (ISC_R_SUCCESS);
+ }
+
+ if (sock->tls.pending_error == ISC_R_SUCCESS) {
+ return (ISC_R_TLSERROR);
+ }
+
+ result = sock->tls.pending_error;
+ sock->tls.pending_error = ISC_R_SUCCESS;
+
+ return (result);
+}
+
+static isc_result_t
+tls_cycle(isc_nmsocket_t *sock) {
+ isc_result_t result;
+
+ /*
+ * Clear the TLS error queue so that SSL_get_error() and SSL I/O
+ * routine calls will not get affected by prior error statuses.
+ *
+ * See here:
+ * https://www.openssl.org/docs/man3.0/man3/SSL_get_error.html
+ *
+ * In particular, it mentions the following:
+ *
+ * The current thread's error queue must be empty before the
+ * TLS/SSL I/O operation is attempted, or SSL_get_error() will not
+ * work reliably.
+ *
+ * As we use the result of SSL_get_error() to decide on I/O
+ * operations, we need to ensure that it works reliably by
+ * cleaning the error queue.
+ *
+ * The sum of details: https://stackoverflow.com/a/37980911
+ */
+ ERR_clear_error();
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+ result = tls_pop_error(sock);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ if (sock->tls.cycle) {
+ return (ISC_R_SUCCESS);
+ }
+
+ sock->tls.cycle = true;
+ result = tls_cycle_input(sock);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+
+ result = tls_cycle_output(sock);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+done:
+ sock->tls.cycle = false;
+
+ return (result);
+}
+
+static void
+async_tlsdns_cycle(isc_nmsocket_t *sock) {
+ isc__netievent_tlsdnscycle_t *ievent = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ /* Socket was closed midflight by isc__nm_tlsdns_shutdown() */
+ if (isc__nmsocket_closing(sock)) {
+ return;
+ }
+
+ ievent = isc__nm_get_netievent_tlsdnscycle(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tlsdnscycle(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnscycle_t *ievent =
+ (isc__netievent_tlsdnscycle_t *)ev0;
+ isc_result_t result;
+ isc_nmsocket_t *sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ sock = ievent->sock;
+
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ tls_error(sock, result);
+ }
+}
+
+void
+isc__nm_tlsdns_read_cb(uv_stream_t *stream, ssize_t nread,
+ const uv_buf_t *buf) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)stream);
+ size_t len;
+ isc_result_t result;
+ int rv;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->reading));
+ REQUIRE(buf != NULL);
+
+ if (isc__nmsocket_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, true);
+ goto free;
+ }
+
+ if (nread < 0) {
+ if (nread != UV_EOF) {
+ isc__nm_incstats(sock, STATID_RECVFAIL);
+ }
+
+ isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nread), true);
+
+ goto free;
+ }
+
+ if (!atomic_load(&sock->client)) {
+ sock->read_timeout = atomic_load(&sock->mgr->idle);
+ }
+
+ /*
+ * The input has to be fed into BIO
+ */
+ rv = BIO_write_ex(sock->tls.app_wbio, buf->base, (size_t)nread, &len);
+
+ if (rv <= 0 || (size_t)nread != len) {
+ isc__nm_failed_read_cb(sock, ISC_R_TLSERROR, true);
+ goto free;
+ }
+
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_failed_read_cb(sock, result, true);
+ }
+free:
+ async_tlsdns_cycle(sock);
+
+ if (nread < 0) {
+ /*
+ * The buffer may be a null buffer on error.
+ */
+ if (buf->base == NULL && buf->len == 0) {
+ return;
+ }
+ }
+
+ isc__nm_free_uvbuf(sock, buf);
+}
+
+static void
+quota_accept_cb(isc_quota_t *quota, void *sock0) {
+ isc_nmsocket_t *sock = (isc_nmsocket_t *)sock0;
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ /*
+ * Create a tlsdnsaccept event and pass it using the async
+ * channel.
+ */
+
+ isc__netievent_tlsdnsaccept_t *ievent =
+ isc__nm_get_netievent_tlsdnsaccept(sock->mgr, sock, quota);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+/*
+ * This is called after we get a quota_accept_cb() callback.
+ */
+void
+isc__nm_async_tlsdnsaccept(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsaccept_t *ievent =
+ (isc__netievent_tlsdnsaccept_t *)ev0;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ result = accept_connection(ievent->sock, ievent->quota);
+ isc__nm_accept_connection_log(result, can_log_tlsdns_quota());
+}
+
+static isc_result_t
+accept_connection(isc_nmsocket_t *ssock, isc_quota_t *quota) {
+ isc_nmsocket_t *csock = NULL;
+ isc__networker_t *worker = NULL;
+ int r;
+ isc_result_t result;
+ struct sockaddr_storage peer_ss;
+ struct sockaddr_storage local_ss;
+ isc_sockaddr_t local;
+
+ REQUIRE(VALID_NMSOCK(ssock));
+ REQUIRE(ssock->tid == isc_nm_tid());
+
+ if (isc__nmsocket_closing(ssock)) {
+ if (quota != NULL) {
+ isc_quota_detach(&quota);
+ }
+ return (ISC_R_CANCELED);
+ }
+
+ REQUIRE(ssock->accept_cb != NULL);
+
+ csock = isc_mem_get(ssock->mgr->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(csock, ssock->mgr, isc_nm_tlsdnssocket,
+ &ssock->iface);
+ csock->tid = ssock->tid;
+ csock->extrahandlesize = ssock->extrahandlesize;
+ isc__nmsocket_attach(ssock, &csock->server);
+ csock->accept_cb = ssock->accept_cb;
+ csock->accept_cbarg = ssock->accept_cbarg;
+ csock->recv_cb = ssock->recv_cb;
+ csock->recv_cbarg = ssock->recv_cbarg;
+ csock->quota = quota;
+ atomic_init(&csock->accepting, true);
+
+ worker = &csock->mgr->workers[csock->tid];
+
+ r = uv_tcp_init(&worker->loop, &csock->uv_handle.tcp);
+ UV_RUNTIME_CHECK(uv_tcp_init, r);
+ uv_handle_set_data(&csock->uv_handle.handle, csock);
+
+ r = uv_timer_init(&worker->loop, &csock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&csock->read_timer, csock);
+
+ r = uv_accept(&ssock->uv_handle.stream, &csock->uv_handle.stream);
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ r = uv_tcp_getpeername(&csock->uv_handle.tcp,
+ (struct sockaddr *)&peer_ss,
+ &(int){ sizeof(peer_ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&csock->peer,
+ (struct sockaddr *)&peer_ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ r = uv_tcp_getsockname(&csock->uv_handle.tcp,
+ (struct sockaddr *)&local_ss,
+ &(int){ sizeof(local_ss) });
+ if (r != 0) {
+ result = isc__nm_uverr2result(r);
+ goto failure;
+ }
+
+ result = isc_sockaddr_fromsockaddr(&local,
+ (struct sockaddr *)&local_ss);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ csock->tls.state = TLS_STATE_NONE;
+
+ csock->tls.tls = isc_tls_create(ssock->tls.ctx);
+ RUNTIME_CHECK(csock->tls.tls != NULL);
+
+ r = BIO_new_bio_pair(&csock->tls.ssl_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE,
+ &csock->tls.app_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE);
+ RUNTIME_CHECK(r == 1);
+
+ r = BIO_new_bio_pair(&csock->tls.ssl_rbio, ISC_NETMGR_TCP_RECVBUF_SIZE,
+ &csock->tls.app_wbio, ISC_NETMGR_TCP_RECVBUF_SIZE);
+ RUNTIME_CHECK(r == 1);
+
+#if HAVE_SSL_SET0_RBIO && HAVE_SSL_SET0_WBIO
+ /*
+ * Note that if the rbio and wbio are the same then
+ * SSL_set0_rbio() and SSL_set0_wbio() each take ownership of
+ * one reference. Therefore it may be necessary to increment the
+ * number of references available using BIO_up_ref(3) before
+ * calling the set0 functions.
+ */
+ SSL_set0_rbio(csock->tls.tls, csock->tls.ssl_rbio);
+ SSL_set0_wbio(csock->tls.tls, csock->tls.ssl_wbio);
+#else
+ SSL_set_bio(csock->tls.tls, csock->tls.ssl_rbio, csock->tls.ssl_wbio);
+#endif
+
+ SSL_set_accept_state(csock->tls.tls);
+
+ /* FIXME: Set SSL_MODE_RELEASE_BUFFERS */
+
+ atomic_store(&csock->accepting, false);
+
+ isc__nm_incstats(csock, STATID_ACCEPT);
+
+ csock->read_timeout = atomic_load(&csock->mgr->init);
+
+ csock->closehandle_cb = isc__nm_resume_processing;
+
+ /*
+ * We need to keep the handle alive until we fail to read or
+ * connection is closed by the other side, it will be detached
+ * via prep_destroy()->tlsdns_close_direct().
+ *
+ * The handle will be either detached on acceptcb failure or in
+ * the readcb.
+ */
+ csock->recv_handle = isc__nmhandle_get(csock, NULL, &local);
+
+ /*
+ * The initial timer has been set, update the read timeout for
+ * the next reads.
+ */
+ csock->read_timeout = (atomic_load(&csock->keepalive)
+ ? atomic_load(&csock->mgr->keepalive)
+ : atomic_load(&csock->mgr->idle));
+
+ result = isc__nm_process_sock_buffer(csock);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&csock);
+
+ return (ISC_R_SUCCESS);
+
+failure:
+ atomic_store(&csock->active, false);
+
+ isc__nm_failed_accept_cb(csock, result);
+
+ isc__nmsocket_prep_destroy(csock);
+
+ isc__nmsocket_detach(&csock);
+
+ return (result);
+}
+
+void
+isc__nm_tlsdns_send(isc_nmhandle_t *handle, isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc__netievent_tlsdnssend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+
+ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+ *(uint16_t *)uvreq->tcplen = htons(region->length);
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ isc_nmhandle_attach(handle, &uvreq->handle);
+
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+
+ ievent = isc__nm_get_netievent_tlsdnssend(sock->mgr, sock, uvreq);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ return;
+}
+
+/*
+ * Handle 'tcpsend' async event - send a packet on the socket
+ */
+void
+isc__nm_async_tlsdnssend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc_result_t result;
+ isc__netievent_tlsdnssend_t *ievent =
+ (isc__netievent_tlsdnssend_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+
+ UNUSED(worker);
+
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->write_timeout == 0) {
+ sock->write_timeout =
+ (atomic_load(&sock->keepalive)
+ ? atomic_load(&sock->mgr->keepalive)
+ : atomic_load(&sock->mgr->idle));
+ }
+
+ result = tlsdns_send_direct(sock, uvreq);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq, result);
+ }
+}
+
+static void
+tlsdns_send_enqueue(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc__netievent_tlsdnssend_t *ievent =
+ isc__nm_get_netievent_tlsdnssend(sock->mgr, sock, req);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+static isc_result_t
+tlsdns_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc_result_t result;
+ int err = 0;
+ int rv;
+ size_t bytes = 0;
+ size_t sendlen;
+ isc__networker_t *worker = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+
+ result = tls_pop_error(sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+ /* Writes won't succeed until handshake end */
+ if (!SSL_is_init_finished(sock->tls.tls)) {
+ goto requeue;
+ }
+
+ /*
+ * Try to send any pending data before trying to call SSL_write_ex().
+ * Otherwise, it could fail with SSL_ERROR_WANT_WRITE error.
+ *
+ * It is important to stress that we want to avoid this happening
+ * due to how SSL_write_ex() works - mainly to avoid partial
+ * writes.
+ *
+ * Although the documentation for these functions is vague, it is
+ * not stated that partial writes are not possible. On the
+ * contrary, one can deduce that it is possible and recovering
+ * from this situation is complicated and unreasonably hard to
+ * implement to the point when it is better to avoid this
+ * situation altogether.
+ *
+ * In particular, the following can be found in the documentation:
+ *
+ * "The write functions will only return with success when the
+ * complete contents of buf of length num has been written. This
+ * default behaviour can be changed with the
+ * SSL_MODE_ENABLE_PARTIAL_WRITE option of
+ * SSL_CTX_set_mode(3). When this flag is set, the write functions
+ * will also return with success when a partial write has been
+ * successfully completed. In this case, the write function
+ * operation is considered completed. The bytes are sent, and a
+ * new write call with a new buffer (with the already sent bytes
+ * removed) must be started. A partial write is performed with the
+ * size of a message block, which is 16kB."
+
+ * That is, it is said that success is returned only when the
+ * complete chunk of data is written (encrypted), but it does not
+ * mention that partial writes are not possible (the behaviour can
+ * be changed using SSL_MODE_ENABLE_PARTIAL_WRITE). Another
+ * important aspect of this passage is that a partial write of up
+ * to 16 kilobytes can happen, and the call still can
+ * fail. Needless to say, this amount of data may include more
+ * than one DNS message.
+ *
+ * One could expect that SSL_write_ex() should return the number
+ * of bytes written, but no, that is not guaranteed (emphasis is
+ * mine): "*On success* SSL_write_ex() will store the number of
+ * bytes written in *written."
+ *
+ * Moreover, we can find the following guidance on how to handle
+ * the SSL_ERROR_WANT_WRITE error in the "Warnings" section of the
+ * documentation:
+
+ * "When a write function call has to be repeated because
+ * SSL_get_error(3) returned SSL_ERROR_WANT_READ or
+ * SSL_ERROR_WANT_WRITE, it must be repeated with the same
+ * arguments. The data that was passed might have been partially
+ * processed. When SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER was set
+ * using SSL_CTX_set_mode(3) the pointer can be different, but the
+ * data and length should still be the same."
+ *
+ * That is, when a call to SSL_write_ex() fails with
+ * SSL_ERROR_WANT_WRITE, we must attempt to make the next call to
+ * the function exactly with the same arguments. Of course, the
+ * code is structured in such a way that we cannot guarantee that
+ * (and keeping track of that would be unreasonably complicated to
+ * implement). The best we can do to avoid this error is to get
+ * (and send) the outgoing data from the SSL buffer ASAP before
+ * processing the subsequent write request. We can achieve that by
+ * calling tls_cycle() and rescheduling the write request for
+ * being processed later.
+ */
+ if (BIO_pending(sock->tls.app_rbio) > 0) {
+ /* Handle any pending data and requeue the write request. */
+ goto cycle;
+ }
+
+ /*
+ * There's no SSL_writev(), so we need to use a local buffer to
+ * assemble the whole message
+ */
+ worker = &sock->mgr->workers[sock->tid];
+ sendlen = req->uvbuf.len + sizeof(uint16_t);
+ memmove(worker->sendbuf, req->tcplen, sizeof(uint16_t));
+ memmove(worker->sendbuf + sizeof(uint16_t), req->uvbuf.base,
+ req->uvbuf.len);
+
+ rv = SSL_write_ex(sock->tls.tls, worker->sendbuf, sendlen, &bytes);
+ if (rv > 0) {
+ INSIST(sendlen == bytes);
+
+ ISC_LIST_APPEND(sock->tls.sendreqs, req, link);
+ async_tlsdns_cycle(sock);
+ return (ISC_R_SUCCESS);
+ }
+
+ /* Nothing was written, maybe enqueue? */
+ err = SSL_get_error(sock->tls.tls, rv);
+
+ switch (err) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ break;
+ case 0:
+ UNREACHABLE();
+ default:
+ return (ISC_R_TLSERROR);
+ }
+
+cycle:
+ result = tls_cycle(sock);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+requeue:
+ tlsdns_send_enqueue(sock, req);
+
+ return (result);
+}
+
+static void
+tlsdns_stop_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ uv_handle_set_data(handle, NULL);
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ atomic_store(&sock->listening, false);
+
+ BIO_free_all(sock->tls.app_rbio);
+ BIO_free_all(sock->tls.app_wbio);
+
+ if (sock->tls.ctx != NULL) {
+ isc_tlsctx_free(&sock->tls.ctx);
+ }
+
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+tlsdns_close_sock(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ if (sock->server != NULL) {
+ isc__nmsocket_detach(&sock->server);
+ }
+
+ atomic_store(&sock->connected, false);
+
+ if (sock->tls.tls != NULL) {
+ /*
+ * Let's shutdown the TLS session properly so that the session
+ * will remain resumable, if required.
+ */
+ tlsdns_set_tls_shutdown(sock->tls.tls);
+ tlsdns_keep_client_tls_session(sock);
+ isc_tls_free(&sock->tls.tls);
+ }
+
+ BIO_free_all(sock->tls.app_rbio);
+ BIO_free_all(sock->tls.app_wbio);
+
+ if (sock->tls.ctx != NULL) {
+ isc_tlsctx_free(&sock->tls.ctx);
+ }
+
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tlsdns_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ tlsdns_close_sock(sock);
+}
+
+static void
+read_timer_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ if (sock->parent) {
+ uv_close(&sock->uv_handle.handle, tlsdns_stop_cb);
+ } else if (uv_is_closing(&sock->uv_handle.handle)) {
+ tlsdns_close_sock(sock);
+ } else {
+ uv_close(&sock->uv_handle.handle, tlsdns_close_cb);
+ }
+}
+
+static void
+stop_tlsdns_child(isc_nmsocket_t *sock) {
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ tlsdns_close_direct(sock);
+
+ atomic_fetch_sub(&sock->parent->rchildren, 1);
+
+ isc_barrier_wait(&sock->parent->stoplistening);
+}
+
+static void
+stop_tlsdns_parent(isc_nmsocket_t *sock) {
+ isc_nmsocket_t *csock = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tlsdnslistener);
+
+ isc_barrier_init(&sock->stoplistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ csock = &sock->children[i];
+
+ REQUIRE(VALID_NMSOCK(csock));
+
+ if ((int)i == isc_nm_tid()) {
+ /*
+ * We need to schedule closing the other sockets first
+ */
+ continue;
+ }
+
+ atomic_store(&csock->active, false);
+ enqueue_stoplistening(csock);
+ }
+
+ csock = &sock->children[isc_nm_tid()];
+ atomic_store(&csock->active, false);
+ stop_tlsdns_child(csock);
+
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+tlsdns_close_direct(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ REQUIRE(sock->tls.pending_req == NULL);
+
+ if (sock->quota != NULL) {
+ isc_quota_detach(&sock->quota);
+ }
+
+ if (sock->recv_handle != NULL) {
+ isc_nmhandle_detach(&sock->recv_handle);
+ }
+
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+ uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb);
+}
+
+void
+isc__nm_tlsdns_close(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ if (sock->tid == isc_nm_tid()) {
+ tlsdns_close_direct(sock);
+ } else {
+ /*
+ * We need to create an event and pass it using async
+ * channel
+ */
+ isc__netievent_tlsdnsclose_t *ievent =
+ isc__nm_get_netievent_tlsdnsclose(sock->mgr, sock);
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_tlsdnsclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnsclose_t *ievent =
+ (isc__netievent_tlsdnsclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ tlsdns_close_direct(sock);
+}
+
+static void
+tlsdns_close_connect_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+
+ REQUIRE(VALID_NMSOCK(sock));
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ isc__nmsocket_prep_destroy(sock);
+ isc__nmsocket_detach(&sock);
+}
+
+void
+isc__nm_tlsdns_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+
+ /*
+ * If the socket is active, mark it inactive and
+ * continue. If it isn't active, stop now.
+ */
+ if (!isc__nmsocket_deactivate(sock)) {
+ return;
+ }
+
+ if (sock->tls.tls) {
+ /* Shutdown any active TLS connections */
+ tlsdns_set_tls_shutdown(sock->tls.tls);
+ }
+
+ if (atomic_load(&sock->accepting)) {
+ return;
+ }
+
+ /* TLS handshake hasn't been completed yet */
+ if (atomic_load(&sock->connecting)) {
+ isc_nmsocket_t *tsock = NULL;
+
+ /*
+ * TCP connection has been established, now waiting on
+ * TLS handshake to complete
+ */
+ if (sock->tls.pending_req != NULL) {
+ isc_result_t result = ISC_R_CANCELED;
+ isc__nm_uvreq_t *req = sock->tls.pending_req;
+ sock->tls.pending_req = NULL;
+
+ if (peer_verification_has_failed(sock)) {
+ /*
+ * Save error message as 'sock->tls' will get
+ * detached.
+ */
+ sock->tls.tls_verify_errmsg =
+ isc_tls_verify_peer_result_string(
+ sock->tls.tls);
+ result = ISC_R_TLSBADPEERCERT;
+ }
+ isc__nm_failed_connect_cb(sock, req, result, false);
+ return;
+ }
+
+ /* The TCP connection hasn't been established yet */
+ isc__nmsocket_attach(sock, &tsock);
+ uv_close(&sock->uv_handle.handle, tlsdns_close_connect_cb);
+ return;
+ }
+
+ if (sock->statichandle != NULL) {
+ if (isc__nm_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false);
+ } else {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ }
+ return;
+ }
+
+ /*
+ * Otherwise, we just send the socket to abyss...
+ */
+ if (sock->parent == NULL) {
+ isc__nmsocket_prep_destroy(sock);
+ }
+}
+
+void
+isc__nm_tlsdns_cancelread(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tlsdnscancel_t *ievent = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+
+ ievent = isc__nm_get_netievent_tlsdnscancel(sock->mgr, sock, handle);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tlsdnscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdnscancel_t *ievent =
+ (isc__netievent_tlsdnscancel_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ isc__nm_failed_read_cb(sock, ISC_R_EOF, false);
+}
+
+/* Zone transfers/updates over TLS are allowed only when "dot" ALPN
+ * was negotiated.
+ *
+ * Per the XoT spec, we must also check that the TLS version is >=
+ * 1.3. The check could be added here. However, we still need to
+ * support platforms where no cryptographic library with TLSv1.3
+ * support is available. As a result of this we cannot be too strict
+ * regarding the minimal TLS protocol version in order to make it
+ * possible to do encrypted zone transfers over TLSv1.2, as it would
+ * not be right to leave users on these platforms without means for
+ * encrypted zone transfers using BIND only.
+ *
+ * The ones requiring strict compatibility with the specification
+ * could disable TLSv1.2 in the configuration file.
+ */
+isc_result_t
+isc__nm_tlsdns_xfr_checkperm(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlsdnssocket);
+
+ if (!sock->tls.alpn_negotiated) {
+ return (ISC_R_DOTALPNERROR);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+const char *
+isc__nm_tlsdns_verify_tls_peer_result_string(const isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlsdnssocket);
+
+ sock = handle->sock;
+ if (sock->tls.tls == NULL) {
+ return (sock->tls.tls_verify_errmsg);
+ }
+
+ return (isc_tls_verify_peer_result_string(sock->tls.tls));
+}
+
+void
+isc__nm_async_tlsdns_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx,
+ const int tid) {
+ REQUIRE(tid >= 0);
+
+ isc_tlsctx_free(&listener->children[tid].tls.ctx);
+ isc_tlsctx_attach(tlsctx, &listener->children[tid].tls.ctx);
+}
+
+void
+isc__nm_tlsdns_cleanup_data(isc_nmsocket_t *sock) {
+ if (sock->type == isc_nm_tlsdnslistener ||
+ sock->type == isc_nm_tlsdnssocket)
+ {
+ if (sock->tls.client_sess_cache != NULL) {
+ INSIST(atomic_load(&sock->client));
+ INSIST(sock->type == isc_nm_tlsdnssocket);
+ isc_tlsctx_client_session_cache_detach(
+ &sock->tls.client_sess_cache);
+ }
+ if (sock->tls.ctx != NULL) {
+ INSIST(ISC_LIST_EMPTY(sock->tls.sendreqs));
+ isc_tlsctx_free(&sock->tls.ctx);
+ }
+ }
+}
+
+static void
+tlsdns_keep_client_tls_session(isc_nmsocket_t *sock) {
+ /*
+ * Ensure that the isc_tls_t is being accessed from
+ * within the worker thread the socket is bound to.
+ */
+ REQUIRE(sock->tid == isc_nm_tid());
+ if (sock->tls.client_sess_cache != NULL &&
+ sock->tls.client_session_saved == false)
+ {
+ INSIST(atomic_load(&sock->client));
+ isc_tlsctx_client_session_cache_keep_sockaddr(
+ sock->tls.client_sess_cache, &sock->peer,
+ sock->tls.tls);
+ sock->tls.client_session_saved = true;
+ }
+}
diff --git a/lib/isc/netmgr/tlsstream.c b/lib/isc/netmgr/tlsstream.c
new file mode 100644
index 0000000..7b49071
--- /dev/null
+++ b/lib/isc/netmgr/tlsstream.c
@@ -0,0 +1,1348 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <libgen.h>
+#include <unistd.h>
+#include <uv.h>
+
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include <isc/atomic.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/once.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/stdtime.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "../openssl_shim.h"
+#include "netmgr-int.h"
+#include "uv-compat.h"
+
+#define TLS_BUF_SIZE (UINT16_MAX)
+
+static isc_result_t
+tls_error_to_result(const int tls_err, const int tls_state, isc_tls_t *tls) {
+ switch (tls_err) {
+ case SSL_ERROR_ZERO_RETURN:
+ return (ISC_R_EOF);
+ case SSL_ERROR_SSL:
+ if (tls != NULL && tls_state < TLS_IO &&
+ SSL_get_verify_result(tls) != X509_V_OK)
+ {
+ return (ISC_R_TLSBADPEERCERT);
+ }
+ return (ISC_R_TLSERROR);
+ default:
+ return (ISC_R_UNEXPECTED);
+ }
+}
+
+static void
+tls_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result);
+
+static void
+tls_do_bio(isc_nmsocket_t *sock, isc_region_t *received_data,
+ isc__nm_uvreq_t *send_data, bool finish);
+
+static void
+tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+ void *cbarg);
+
+static void
+tls_close_direct(isc_nmsocket_t *sock);
+
+static void
+async_tls_do_bio(isc_nmsocket_t *sock);
+
+static void
+tls_init_listener_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *ctx);
+
+static void
+tls_cleanup_listener_tlsctx(isc_nmsocket_t *listener);
+
+static isc_tlsctx_t *
+tls_get_listener_tlsctx(isc_nmsocket_t *listener, const int tid);
+
+static void
+tls_keep_client_tls_session(isc_nmsocket_t *sock);
+
+static void
+tls_try_shutdown(isc_tls_t *tls, const bool quite);
+
+/*
+ * The socket is closing, outerhandle has been detached, listener is
+ * inactive, or the netmgr is closing: any operation on it should abort
+ * with ISC_R_CANCELED.
+ */
+static bool
+inactive(isc_nmsocket_t *sock) {
+ return (!isc__nmsocket_active(sock) || atomic_load(&sock->closing) ||
+ sock->outerhandle == NULL ||
+ !isc__nmsocket_active(sock->outerhandle->sock) ||
+ atomic_load(&sock->outerhandle->sock->closing) ||
+ (sock->listener != NULL &&
+ !isc__nmsocket_active(sock->listener)) ||
+ isc__nm_closing(sock));
+}
+
+static void
+tls_call_connect_cb(isc_nmsocket_t *sock, isc_nmhandle_t *handle,
+ const isc_result_t result) {
+ if (sock->connect_cb == NULL) {
+ return;
+ }
+ sock->connect_cb(handle, result, sock->connect_cbarg);
+ if (result != ISC_R_SUCCESS) {
+ isc__nmsocket_clearcb(handle->sock);
+ }
+}
+
+static void
+tls_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *cbarg) {
+ isc_nmsocket_tls_send_req_t *send_req =
+ (isc_nmsocket_tls_send_req_t *)cbarg;
+ isc_nmsocket_t *tlssock = NULL;
+ bool finish = send_req->finish;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(VALID_NMSOCK(send_req->tlssock));
+
+ tlssock = send_req->tlssock;
+ send_req->tlssock = NULL;
+
+ if (finish) {
+ tls_try_shutdown(tlssock->tlsstream.tls, true);
+ }
+
+ if (send_req->cb != NULL) {
+ INSIST(VALID_NMHANDLE(tlssock->statichandle));
+ send_req->cb(send_req->handle, eresult, send_req->cbarg);
+ isc_nmhandle_detach(&send_req->handle);
+ /* The last handle has been just detached: close the underlying
+ * socket. */
+ if (tlssock->statichandle == NULL) {
+ finish = true;
+ }
+ }
+
+ /* We are tying to avoid a memory allocation for small write
+ * requests. See the mirroring code in the tls_send_outgoing()
+ * function. */
+ if (send_req->data.length > sizeof(send_req->smallbuf)) {
+ isc_mem_put(handle->sock->mgr->mctx, send_req->data.base,
+ send_req->data.length);
+ } else {
+ INSIST(&send_req->smallbuf[0] == send_req->data.base);
+ }
+ isc_mem_put(handle->sock->mgr->mctx, send_req, sizeof(*send_req));
+ tlssock->tlsstream.nsending--;
+
+ if (finish && eresult == ISC_R_SUCCESS) {
+ tlssock->tlsstream.reading = false;
+ isc_nm_cancelread(handle);
+ } else if (eresult == ISC_R_SUCCESS) {
+ tls_do_bio(tlssock, NULL, NULL, false);
+ } else if (eresult != ISC_R_SUCCESS &&
+ tlssock->tlsstream.state <= TLS_HANDSHAKE &&
+ !tlssock->tlsstream.server)
+ {
+ /*
+ * We are still waiting for the handshake to complete, but
+ * it isn't going to happen. Call the connect callback,
+ * passing the error code there.
+ *
+ * (Note: tls_failed_read_cb() calls the connect
+ * rather than the read callback in this case.
+ * XXX: clarify?)
+ */
+ tls_failed_read_cb(tlssock, eresult);
+ }
+
+ isc__nmsocket_detach(&tlssock);
+}
+
+static void
+tls_failed_read_cb(isc_nmsocket_t *sock, const isc_result_t result) {
+ bool destroy = true;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ if (!sock->tlsstream.server &&
+ (sock->tlsstream.state == TLS_INIT ||
+ sock->tlsstream.state == TLS_HANDSHAKE) &&
+ sock->connect_cb != NULL)
+ {
+ isc_nmhandle_t *handle = NULL;
+ INSIST(sock->statichandle == NULL);
+ handle = isc__nmhandle_get(sock, &sock->peer, &sock->iface);
+ tls_call_connect_cb(sock, handle, result);
+ isc__nmsocket_clearcb(sock);
+ isc_nmhandle_detach(&handle);
+ } else if (sock->recv_cb != NULL && sock->statichandle != NULL &&
+ (sock->recv_read || result == ISC_R_TIMEDOUT))
+ {
+ isc__nm_uvreq_t *req = NULL;
+ INSIST(VALID_NMHANDLE(sock->statichandle));
+ sock->recv_read = false;
+ req = isc__nm_uvreq_get(sock->mgr, sock);
+ req->cb.recv = sock->recv_cb;
+ req->cbarg = sock->recv_cbarg;
+ isc_nmhandle_attach(sock->statichandle, &req->handle);
+ if (result != ISC_R_TIMEDOUT) {
+ isc__nmsocket_clearcb(sock);
+ }
+ isc__nm_readcb(sock, req, result);
+ if (result == ISC_R_TIMEDOUT &&
+ (sock->outerhandle == NULL ||
+ isc__nmsocket_timer_running(sock->outerhandle->sock)))
+ {
+ destroy = false;
+ }
+ }
+
+ if (destroy) {
+ isc__nmsocket_prep_destroy(sock);
+ }
+}
+
+static void
+async_tls_do_bio(isc_nmsocket_t *sock) {
+ isc__netievent_tlsdobio_t *ievent =
+ isc__nm_get_netievent_tlsdobio(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+static int
+tls_send_outgoing(isc_nmsocket_t *sock, bool finish, isc_nmhandle_t *tlshandle,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc_nmsocket_tls_send_req_t *send_req = NULL;
+ int pending;
+ int rv;
+ size_t len = 0;
+
+ if (inactive(sock)) {
+ if (cb != NULL) {
+ INSIST(VALID_NMHANDLE(tlshandle));
+ cb(tlshandle, ISC_R_CANCELED, cbarg);
+ }
+ return (0);
+ }
+
+ if (finish) {
+ tls_try_shutdown(sock->tlsstream.tls, false);
+ tls_keep_client_tls_session(sock);
+ }
+
+ pending = BIO_pending(sock->tlsstream.bio_out);
+ if (pending <= 0) {
+ return (pending);
+ }
+
+ /* TODO Should we keep track of these requests in a list? */
+ if ((unsigned int)pending > TLS_BUF_SIZE) {
+ pending = TLS_BUF_SIZE;
+ }
+
+ send_req = isc_mem_get(sock->mgr->mctx, sizeof(*send_req));
+ *send_req = (isc_nmsocket_tls_send_req_t){ .finish = finish,
+ .data.length = pending };
+
+ /* Let's try to avoid a memory allocation for small write requests */
+ if ((size_t)pending > sizeof(send_req->smallbuf)) {
+ send_req->data.base = isc_mem_get(sock->mgr->mctx, pending);
+ } else {
+ send_req->data.base = &send_req->smallbuf[0];
+ }
+
+ isc__nmsocket_attach(sock, &send_req->tlssock);
+ if (cb != NULL) {
+ send_req->cb = cb;
+ send_req->cbarg = cbarg;
+ isc_nmhandle_attach(tlshandle, &send_req->handle);
+ }
+
+ rv = BIO_read_ex(sock->tlsstream.bio_out, send_req->data.base, pending,
+ &len);
+ /* There's something pending, read must succeed */
+ RUNTIME_CHECK(rv == 1);
+
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+
+ sock->tlsstream.nsending++;
+ isc_nm_send(sock->outerhandle, &send_req->data, tls_senddone, send_req);
+
+ return (pending);
+}
+
+static int
+tls_process_outgoing(isc_nmsocket_t *sock, bool finish,
+ isc__nm_uvreq_t *send_data) {
+ int pending;
+
+ bool received_shutdown = ((SSL_get_shutdown(sock->tlsstream.tls) &
+ SSL_RECEIVED_SHUTDOWN) != 0);
+ bool sent_shutdown = ((SSL_get_shutdown(sock->tlsstream.tls) &
+ SSL_SENT_SHUTDOWN) != 0);
+
+ if (received_shutdown && !sent_shutdown) {
+ finish = true;
+ }
+
+ /* Data from TLS to network */
+ if (send_data != NULL) {
+ pending = tls_send_outgoing(sock, finish, send_data->handle,
+ send_data->cb.send,
+ send_data->cbarg);
+ } else {
+ pending = tls_send_outgoing(sock, finish, NULL, NULL, NULL);
+ }
+
+ return (pending);
+}
+
+static int
+tls_try_handshake(isc_nmsocket_t *sock, isc_result_t *presult) {
+ int rv = 0;
+ isc_nmhandle_t *tlshandle = NULL;
+
+ REQUIRE(sock->tlsstream.state == TLS_HANDSHAKE);
+
+ if (SSL_is_init_finished(sock->tlsstream.tls) == 1) {
+ return (0);
+ }
+
+ rv = SSL_do_handshake(sock->tlsstream.tls);
+ if (rv == 1) {
+ isc_result_t result = ISC_R_SUCCESS;
+ INSIST(SSL_is_init_finished(sock->tlsstream.tls) == 1);
+ INSIST(sock->statichandle == NULL);
+ isc__nmsocket_log_tls_session_reuse(sock, sock->tlsstream.tls);
+ tlshandle = isc__nmhandle_get(sock, &sock->peer, &sock->iface);
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ }
+
+ if (sock->tlsstream.server) {
+ if (isc__nmsocket_closing(sock->listener)) {
+ result = ISC_R_CANCELED;
+ } else if (result == ISC_R_SUCCESS) {
+ result = sock->listener->accept_cb(
+ tlshandle, result,
+ sock->listener->accept_cbarg);
+ }
+ } else {
+ tls_call_connect_cb(sock, tlshandle, result);
+ }
+ isc_nmhandle_detach(&tlshandle);
+ sock->tlsstream.state = TLS_IO;
+
+ if (presult != NULL) {
+ *presult = result;
+ }
+ }
+
+ return (rv);
+}
+
+static bool
+tls_try_to_close_unused_socket(isc_nmsocket_t *sock) {
+ if (sock->tlsstream.state > TLS_HANDSHAKE &&
+ sock->statichandle == NULL && sock->tlsstream.nsending == 0)
+ {
+ /*
+ * It seems that no action on the socket has been
+ * scheduled on some point after the handshake, let's
+ * close the connection.
+ */
+ isc__nmsocket_prep_destroy(sock);
+ return (true);
+ }
+
+ return (false);
+}
+
+static void
+tls_do_bio(isc_nmsocket_t *sock, isc_region_t *received_data,
+ isc__nm_uvreq_t *send_data, bool finish) {
+ isc_result_t result = ISC_R_SUCCESS;
+ int pending, tls_status = SSL_ERROR_NONE;
+ int rv = 0;
+ size_t len = 0;
+ int saved_errno = 0;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ /* We will resume read if TLS layer wants us to */
+ if (sock->tlsstream.reading && sock->outerhandle) {
+ REQUIRE(VALID_NMHANDLE(sock->outerhandle));
+ isc_nm_pauseread(sock->outerhandle);
+ }
+
+ /*
+ * Clear the TLS error queue so that SSL_get_error() and SSL I/O
+ * routine calls will not get affected by prior error statuses.
+ *
+ * See here:
+ * https://www.openssl.org/docs/man3.0/man3/SSL_get_error.html
+ *
+ * In particular, it mentions the following:
+ *
+ * The current thread's error queue must be empty before the
+ * TLS/SSL I/O operation is attempted, or SSL_get_error() will not
+ * work reliably.
+ *
+ * As we use the result of SSL_get_error() to decide on I/O
+ * operations, we need to ensure that it works reliably by
+ * cleaning the error queue.
+ *
+ * The sum of details: https://stackoverflow.com/a/37980911
+ */
+ ERR_clear_error();
+
+ if (sock->tlsstream.state == TLS_INIT) {
+ INSIST(received_data == NULL && send_data == NULL);
+ if (sock->tlsstream.server) {
+ SSL_set_accept_state(sock->tlsstream.tls);
+ } else {
+ SSL_set_connect_state(sock->tlsstream.tls);
+ }
+ sock->tlsstream.state = TLS_HANDSHAKE;
+ rv = tls_try_handshake(sock, NULL);
+ INSIST(SSL_is_init_finished(sock->tlsstream.tls) == 0);
+ } else if (sock->tlsstream.state == TLS_CLOSED) {
+ return;
+ } else { /* initialised and doing I/O */
+ if (received_data != NULL) {
+ INSIST(send_data == NULL);
+ rv = BIO_write_ex(sock->tlsstream.bio_in,
+ received_data->base,
+ received_data->length, &len);
+ if (rv <= 0 || len != received_data->length) {
+ result = ISC_R_TLSERROR;
+#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE)
+ saved_errno = errno;
+#endif
+ goto error;
+ }
+
+ /*
+ * Only after doing the IO we can check whether SSL
+ * handshake is done.
+ */
+ if (sock->tlsstream.state == TLS_HANDSHAKE) {
+ isc_result_t hs_result = ISC_R_UNSET;
+ rv = tls_try_handshake(sock, &hs_result);
+ if (sock->tlsstream.state == TLS_IO &&
+ hs_result != ISC_R_SUCCESS)
+ {
+ /*
+ * The accept callback has been called
+ * unsuccessfully. Let's try to shut
+ * down the TLS connection gracefully.
+ */
+ INSIST(SSL_is_init_finished(
+ sock->tlsstream.tls) ==
+ 1);
+ finish = true;
+ }
+ }
+ } else if (send_data != NULL) {
+ INSIST(received_data == NULL);
+ INSIST(sock->tlsstream.state > TLS_HANDSHAKE);
+ bool received_shutdown =
+ ((SSL_get_shutdown(sock->tlsstream.tls) &
+ SSL_RECEIVED_SHUTDOWN) != 0);
+ bool sent_shutdown =
+ ((SSL_get_shutdown(sock->tlsstream.tls) &
+ SSL_SENT_SHUTDOWN) != 0);
+ rv = SSL_write_ex(sock->tlsstream.tls,
+ send_data->uvbuf.base,
+ send_data->uvbuf.len, &len);
+ if (rv != 1 || len != send_data->uvbuf.len) {
+ result = received_shutdown || sent_shutdown
+ ? ISC_R_CANCELED
+ : ISC_R_TLSERROR;
+ send_data->cb.send(send_data->handle, result,
+ send_data->cbarg);
+ send_data = NULL;
+ return;
+ }
+ }
+
+ /* Decrypt and pass data from network to client */
+ if (sock->tlsstream.state >= TLS_IO && sock->recv_cb != NULL &&
+ !atomic_load(&sock->readpaused) &&
+ sock->statichandle != NULL && !finish)
+ {
+ uint8_t recv_buf[TLS_BUF_SIZE];
+ INSIST(sock->tlsstream.state > TLS_HANDSHAKE);
+ while ((rv = SSL_read_ex(sock->tlsstream.tls, recv_buf,
+ TLS_BUF_SIZE, &len)) == 1)
+ {
+ isc_region_t region;
+ region = (isc_region_t){ .base = &recv_buf[0],
+ .length = len };
+
+ INSIST(VALID_NMHANDLE(sock->statichandle));
+ sock->recv_cb(sock->statichandle, ISC_R_SUCCESS,
+ &region, sock->recv_cbarg);
+ /* The handle could have been detached in
+ * sock->recv_cb, making the sock->statichandle
+ * nullified (it happens in netmgr.c). If it is
+ * the case, then it means that we are not
+ * interested in keeping the connection alive
+ * anymore. Let's shut down the SSL session,
+ * send what we have in the SSL buffers,
+ * and close the connection.
+ */
+ if (sock->statichandle == NULL) {
+ finish = true;
+ break;
+ } else if (sock->recv_cb == NULL) {
+ /*
+ * The 'sock->recv_cb' might have been
+ * nullified during the call to
+ * 'sock->recv_cb'. That could happen,
+ * indirectly when wrapping up.
+ *
+ * In this case, let's close the TLS
+ * connection.
+ */
+ finish = true;
+ break;
+ } else if (atomic_load(&sock->readpaused)) {
+ /*
+ * Reading has been paused from withing
+ * the context of read callback - stop
+ * processing incoming data.
+ */
+ break;
+ }
+ }
+ }
+ }
+ errno = 0;
+ tls_status = SSL_get_error(sock->tlsstream.tls, rv);
+ saved_errno = errno;
+
+ /* See "BUGS" section at:
+ * https://www.openssl.org/docs/man1.1.1/man3/SSL_get_error.html
+ *
+ * It is mentioned there that when TLS status equals
+ * SSL_ERROR_SYSCALL AND errno == 0 it means that underlying
+ * transport layer returned EOF prematurely. However, we are
+ * managing the transport ourselves, so we should just resume
+ * reading from the TCP socket.
+ *
+ * It seems that this case has been handled properly on modern
+ * versions of OpenSSL. That being said, the situation goes in
+ * line with the manual: it is briefly mentioned there that
+ * SSL_ERROR_SYSCALL might be returned not only in a case of
+ * low-level errors (like system call failures).
+ */
+ if (tls_status == SSL_ERROR_SYSCALL && saved_errno == 0 &&
+ received_data == NULL && send_data == NULL && finish == false)
+ {
+ tls_status = SSL_ERROR_WANT_READ;
+ }
+
+ pending = tls_process_outgoing(sock, finish, send_data);
+ if (pending > 0 && tls_status != SSL_ERROR_SSL) {
+ /* We'll continue in tls_senddone */
+ return;
+ }
+
+ switch (tls_status) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ (void)tls_try_to_close_unused_socket(sock);
+ return;
+ case SSL_ERROR_WANT_WRITE:
+ if (sock->tlsstream.nsending == 0) {
+ /*
+ * Launch tls_do_bio asynchronously. If we're sending
+ * already the send callback will call it.
+ */
+ async_tls_do_bio(sock);
+ }
+ return;
+ case SSL_ERROR_WANT_READ:
+ if (tls_try_to_close_unused_socket(sock) ||
+ sock->outerhandle == NULL || atomic_load(&sock->readpaused))
+ {
+ return;
+ }
+
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+
+ if (sock->tlsstream.reading) {
+ isc_nm_resumeread(sock->outerhandle);
+ } else if (sock->tlsstream.state == TLS_HANDSHAKE) {
+ sock->tlsstream.reading = true;
+ isc_nm_read(sock->outerhandle, tls_readcb, sock);
+ }
+ return;
+ default:
+ result = tls_error_to_result(tls_status, sock->tlsstream.state,
+ sock->tlsstream.tls);
+ break;
+ }
+
+error:
+#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE)
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ ISC_LOG_NOTICE,
+ "SSL error in BIO: %d %s (errno: %d). Arguments: "
+ "received_data: %p, "
+ "send_data: %p, finish: %s",
+ tls_status, isc_result_totext(result), saved_errno,
+ received_data, send_data, finish ? "true" : "false");
+#endif
+ tls_failed_read_cb(sock, result);
+}
+
+static void
+tls_readcb(isc_nmhandle_t *handle, isc_result_t result, isc_region_t *region,
+ void *cbarg) {
+ isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg;
+
+ REQUIRE(VALID_NMSOCK(tlssock));
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(tlssock->tid == isc_nm_tid());
+
+ if (result != ISC_R_SUCCESS) {
+ tls_failed_read_cb(tlssock, result);
+ return;
+ }
+
+ tls_do_bio(tlssock, region, NULL, false);
+}
+
+static isc_result_t
+initialize_tls(isc_nmsocket_t *sock, bool server) {
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ sock->tlsstream.bio_in = BIO_new(BIO_s_mem());
+ if (sock->tlsstream.bio_in == NULL) {
+ isc_tls_free(&sock->tlsstream.tls);
+ return (ISC_R_TLSERROR);
+ }
+ sock->tlsstream.bio_out = BIO_new(BIO_s_mem());
+ if (sock->tlsstream.bio_out == NULL) {
+ BIO_free_all(sock->tlsstream.bio_in);
+ sock->tlsstream.bio_in = NULL;
+ isc_tls_free(&sock->tlsstream.tls);
+ return (ISC_R_TLSERROR);
+ }
+
+ if (BIO_set_mem_eof_return(sock->tlsstream.bio_in, EOF) != 1 ||
+ BIO_set_mem_eof_return(sock->tlsstream.bio_out, EOF) != 1)
+ {
+ goto error;
+ }
+
+ SSL_set_bio(sock->tlsstream.tls, sock->tlsstream.bio_in,
+ sock->tlsstream.bio_out);
+ sock->tlsstream.server = server;
+ sock->tlsstream.nsending = 0;
+ sock->tlsstream.state = TLS_INIT;
+ return (ISC_R_SUCCESS);
+error:
+ isc_tls_free(&sock->tlsstream.tls);
+ sock->tlsstream.bio_out = sock->tlsstream.bio_in = NULL;
+ return (ISC_R_TLSERROR);
+}
+
+static isc_result_t
+tlslisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ isc_nmsocket_t *tlslistensock = (isc_nmsocket_t *)cbarg;
+ isc_nmsocket_t *tlssock = NULL;
+ isc_tlsctx_t *tlsctx = NULL;
+ int tid;
+
+ /* If accept() was unsuccessful we can't do anything */
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(VALID_NMSOCK(tlslistensock));
+ REQUIRE(tlslistensock->type == isc_nm_tlslistener);
+
+ if (isc__nmsocket_closing(handle->sock) ||
+ isc__nmsocket_closing(tlslistensock) ||
+ !atomic_load(&tlslistensock->listening))
+ {
+ return (ISC_R_CANCELED);
+ }
+
+ /*
+ * We need to create a 'wrapper' tlssocket for this connection.
+ */
+ tlssock = isc_mem_get(handle->sock->mgr->mctx, sizeof(*tlssock));
+ isc__nmsocket_init(tlssock, handle->sock->mgr, isc_nm_tlssocket,
+ &handle->sock->iface);
+
+ tid = isc_nm_tid();
+ /* We need to initialize SSL now to reference SSL_CTX properly */
+ tlsctx = tls_get_listener_tlsctx(tlslistensock, tid);
+ RUNTIME_CHECK(tlsctx != NULL);
+ isc_tlsctx_attach(tlsctx, &tlssock->tlsstream.ctx);
+ tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx);
+ if (tlssock->tlsstream.tls == NULL) {
+ atomic_store(&tlssock->closed, true);
+ isc_tlsctx_free(&tlssock->tlsstream.ctx);
+ isc__nmsocket_detach(&tlssock);
+ return (ISC_R_TLSERROR);
+ }
+
+ tlssock->extrahandlesize = tlslistensock->extrahandlesize;
+ isc__nmsocket_attach(tlslistensock, &tlssock->listener);
+ isc_nmhandle_attach(handle, &tlssock->outerhandle);
+ tlssock->peer = handle->sock->peer;
+ tlssock->read_timeout = atomic_load(&handle->sock->mgr->init);
+ tlssock->tid = tid;
+
+ /*
+ * Hold a reference to tlssock in the TCP socket: it will
+ * detached in isc__nm_tls_cleanup_data().
+ */
+ handle->sock->tlsstream.tlssocket = tlssock;
+
+ result = initialize_tls(tlssock, true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ /* TODO: catch failure code, detach tlssock, and log the error */
+
+ tls_do_bio(tlssock, NULL, NULL, false);
+ return (result);
+}
+
+isc_result_t
+isc_nm_listentls(isc_nm_t *mgr, isc_sockaddr_t *iface,
+ isc_nm_accept_cb_t accept_cb, void *accept_cbarg,
+ size_t extrahandlesize, int backlog, isc_quota_t *quota,
+ SSL_CTX *sslctx, isc_nmsocket_t **sockp) {
+ isc_result_t result;
+ isc_nmsocket_t *tlssock = NULL;
+ isc_nmsocket_t *tsock = NULL;
+
+ REQUIRE(VALID_NM(mgr));
+ if (atomic_load(&mgr->closing)) {
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ tlssock = isc_mem_get(mgr->mctx, sizeof(*tlssock));
+
+ isc__nmsocket_init(tlssock, mgr, isc_nm_tlslistener, iface);
+ tlssock->result = ISC_R_UNSET;
+ tlssock->accept_cb = accept_cb;
+ tlssock->accept_cbarg = accept_cbarg;
+ tlssock->extrahandlesize = extrahandlesize;
+ tls_init_listener_tlsctx(tlssock, sslctx);
+ tlssock->tlsstream.tls = NULL;
+
+ /*
+ * tlssock will be a TLS 'wrapper' around an unencrypted stream.
+ * We set tlssock->outer to a socket listening for a TCP connection.
+ */
+ result = isc_nm_listentcp(mgr, iface, tlslisten_acceptcb, tlssock,
+ extrahandlesize, backlog, quota,
+ &tlssock->outer);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&tlssock->closed, true);
+ isc__nmsocket_detach(&tlssock);
+ return (result);
+ }
+
+ /* wait for listen result */
+ isc__nmsocket_attach(tlssock->outer, &tsock);
+ tlssock->result = result;
+ atomic_store(&tlssock->active, true);
+ INSIST(tlssock->outer->tlsstream.tlslistener == NULL);
+ isc__nmsocket_attach(tlssock, &tlssock->outer->tlsstream.tlslistener);
+ isc__nmsocket_detach(&tsock);
+ INSIST(result != ISC_R_UNSET);
+ tlssock->nchildren = tlssock->outer->nchildren;
+
+ isc__nmsocket_barrier_init(tlssock);
+ atomic_init(&tlssock->rchildren, tlssock->nchildren);
+
+ if (result == ISC_R_SUCCESS) {
+ atomic_store(&tlssock->listening, true);
+ *sockp = tlssock;
+ }
+
+ return (result);
+}
+
+void
+isc__nm_async_tlssend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlssend_t *ievent = (isc__netievent_tlssend_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ ievent->req = NULL;
+
+ if (inactive(sock)) {
+ req->cb.send(req->handle, ISC_R_CANCELED, req->cbarg);
+ goto done;
+ }
+
+ tls_do_bio(sock, NULL, req, false);
+done:
+ isc__nm_uvreq_put(&req, sock);
+ return;
+}
+
+void
+isc__nm_tls_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc__netievent_tlssend_t *ievent = NULL;
+ isc__nm_uvreq_t *uvreq = NULL;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ sock = handle->sock;
+
+ REQUIRE(sock->type == isc_nm_tlssocket);
+
+ uvreq = isc__nm_uvreq_get(sock->mgr, sock);
+ isc_nmhandle_attach(handle, &uvreq->handle);
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ /*
+ * We need to create an event and pass it using async channel
+ */
+ ievent = isc__nm_get_netievent_tlssend(sock->mgr, sock, uvreq);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tlsstartread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsstartread_t *ievent =
+ (isc__netievent_tlsstartread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ tls_do_bio(sock, NULL, NULL, false);
+}
+
+void
+isc__nm_tls_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ isc__netievent_tlsstartread_t *ievent = NULL;
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->statichandle == handle);
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->recv_cb == NULL);
+
+ if (inactive(sock)) {
+ cb(handle, ISC_R_CANCELED, NULL, cbarg);
+ return;
+ }
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+
+ ievent = isc__nm_get_netievent_tlsstartread(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_tls_pauseread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ if (atomic_compare_exchange_strong(&handle->sock->readpaused,
+ &(bool){ false }, true))
+ {
+ if (handle->sock->outerhandle != NULL) {
+ isc_nm_pauseread(handle->sock->outerhandle);
+ }
+ }
+}
+
+void
+isc__nm_tls_resumeread(isc_nmhandle_t *handle) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ if (!atomic_compare_exchange_strong(&handle->sock->readpaused,
+ &(bool){ true }, false))
+ {
+ if (inactive(handle->sock)) {
+ return;
+ }
+
+ async_tls_do_bio(handle->sock);
+ }
+}
+
+static void
+tls_close_direct(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ /*
+ * At this point we're certain that there are no
+ * external references, we can close everything.
+ */
+ if (sock->outerhandle != NULL) {
+ isc_nm_pauseread(sock->outerhandle);
+ isc__nmsocket_clearcb(sock->outerhandle->sock);
+ isc_nmhandle_detach(&sock->outerhandle);
+ }
+
+ if (sock->listener != NULL) {
+ isc__nmsocket_detach(&sock->listener);
+ }
+
+ /* Further cleanup performed in isc__nm_tls_cleanup_data() */
+ atomic_store(&sock->closed, true);
+ atomic_store(&sock->active, false);
+ sock->tlsstream.state = TLS_CLOSED;
+}
+
+void
+isc__nm_tls_close(isc_nmsocket_t *sock) {
+ isc__netievent_tlsclose_t *ievent = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlssocket);
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ ievent = isc__nm_get_netievent_tlsclose(sock->mgr, sock);
+ isc__nm_maybe_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_tlsclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsclose_t *ievent = (isc__netievent_tlsclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+
+ tls_close_direct(sock);
+}
+
+void
+isc__nm_tls_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_tlslistener);
+
+ isc__nmsocket_stop(sock);
+}
+
+static void
+tcp_connected(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
+
+void
+isc_nm_tlsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, isc_tlsctx_t *ctx,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ unsigned int timeout, size_t extrahandlesize) {
+ isc_nmsocket_t *nsock = NULL;
+#if defined(NETMGR_TRACE) && defined(NETMGR_TRACE_VERBOSE)
+ fprintf(stderr, "TLS: isc_nm_tlsconnect(): in net thread: %s\n",
+ isc__nm_in_netthread() ? "yes" : "no");
+#endif /* NETMGR_TRACE */
+
+ REQUIRE(VALID_NM(mgr));
+
+ if (atomic_load(&mgr->closing)) {
+ cb(NULL, ISC_R_SHUTTINGDOWN, cbarg);
+ return;
+ }
+
+ nsock = isc_mem_get(mgr->mctx, sizeof(*nsock));
+ isc__nmsocket_init(nsock, mgr, isc_nm_tlssocket, local);
+ nsock->extrahandlesize = extrahandlesize;
+ nsock->result = ISC_R_UNSET;
+ nsock->connect_cb = cb;
+ nsock->connect_cbarg = cbarg;
+ nsock->connect_timeout = timeout;
+ isc_tlsctx_attach(ctx, &nsock->tlsstream.ctx);
+ atomic_init(&nsock->client, true);
+ if (client_sess_cache != NULL) {
+ INSIST(isc_tlsctx_client_session_cache_getctx(
+ client_sess_cache) == ctx);
+ isc_tlsctx_client_session_cache_attach(
+ client_sess_cache, &nsock->tlsstream.client_sess_cache);
+ }
+
+ isc_nm_tcpconnect(mgr, local, peer, tcp_connected, nsock,
+ nsock->connect_timeout, 0);
+}
+
+static void
+tcp_connected(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ isc_nmsocket_t *tlssock = (isc_nmsocket_t *)cbarg;
+ isc_nmhandle_t *tlshandle = NULL;
+
+ REQUIRE(VALID_NMSOCK(tlssock));
+
+ tlssock->tid = isc_nm_tid();
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+
+ INSIST(VALID_NMHANDLE(handle));
+
+ tlssock->iface = handle->sock->iface;
+ tlssock->peer = handle->sock->peer;
+ if (isc__nm_closing(tlssock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ }
+
+ /*
+ * We need to initialize SSL now to reference SSL_CTX properly.
+ */
+ tlssock->tlsstream.tls = isc_tls_create(tlssock->tlsstream.ctx);
+ if (tlssock->tlsstream.tls == NULL) {
+ result = ISC_R_TLSERROR;
+ goto error;
+ }
+
+ result = initialize_tls(tlssock, false);
+ if (result != ISC_R_SUCCESS) {
+ goto error;
+ }
+ tlssock->peer = isc_nmhandle_peeraddr(handle);
+ isc_nmhandle_attach(handle, &tlssock->outerhandle);
+ atomic_store(&tlssock->active, true);
+
+ if (tlssock->tlsstream.client_sess_cache != NULL) {
+ isc_tlsctx_client_session_cache_reuse_sockaddr(
+ tlssock->tlsstream.client_sess_cache, &tlssock->peer,
+ tlssock->tlsstream.tls);
+ }
+
+ /*
+ * Hold a reference to tlssock in the TCP socket: it will
+ * detached in isc__nm_tls_cleanup_data().
+ */
+ handle->sock->tlsstream.tlssocket = tlssock;
+
+ tls_do_bio(tlssock, NULL, NULL, false);
+ return;
+error:
+ tlshandle = isc__nmhandle_get(tlssock, NULL, NULL);
+ atomic_store(&tlssock->closed, true);
+ tls_call_connect_cb(tlssock, tlshandle, result);
+ isc_nmhandle_detach(&tlshandle);
+ isc__nmsocket_detach(&tlssock);
+}
+
+static void
+tls_cancelread(isc_nmsocket_t *sock) {
+ if (!inactive(sock) && sock->tlsstream.state == TLS_IO) {
+ tls_do_bio(sock, NULL, NULL, true);
+ } else if (sock->outerhandle != NULL) {
+ sock->tlsstream.reading = false;
+ isc_nm_cancelread(sock->outerhandle);
+ }
+}
+
+void
+isc__nm_tls_cancelread(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_tlscancel_t *ievent = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(sock->type == isc_nm_tlssocket);
+
+ if (sock->tid == isc_nm_tid()) {
+ tls_cancelread(sock);
+ } else {
+ ievent = isc__nm_get_netievent_tlscancel(sock->mgr, sock,
+ handle);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_async_tlscancel(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlscancel_t *ievent = (isc__netievent_tlscancel_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(worker->id == sock->tid);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ UNUSED(worker);
+ tls_cancelread(sock);
+}
+
+void
+isc__nm_async_tlsdobio(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_tlsdobio_t *ievent = (isc__netievent_tlsdobio_t *)ev0;
+
+ UNUSED(worker);
+
+ tls_do_bio(ievent->sock, NULL, NULL, false);
+}
+
+void
+isc__nm_tls_cleanup_data(isc_nmsocket_t *sock) {
+ if (sock->type == isc_nm_tcplistener &&
+ sock->tlsstream.tlslistener != NULL)
+ {
+ isc__nmsocket_detach(&sock->tlsstream.tlslistener);
+ } else if (sock->type == isc_nm_tlslistener) {
+ tls_cleanup_listener_tlsctx(sock);
+ } else if (sock->type == isc_nm_tlssocket) {
+ if (sock->tlsstream.tls != NULL) {
+ /*
+ * Let's shut down the TLS session properly so that
+ * the session will remain resumable, if required.
+ */
+ tls_try_shutdown(sock->tlsstream.tls, true);
+ tls_keep_client_tls_session(sock);
+ isc_tls_free(&sock->tlsstream.tls);
+ /* These are destroyed when we free SSL */
+ sock->tlsstream.bio_out = NULL;
+ sock->tlsstream.bio_in = NULL;
+ }
+ if (sock->tlsstream.ctx != NULL) {
+ isc_tlsctx_free(&sock->tlsstream.ctx);
+ }
+ if (sock->tlsstream.client_sess_cache != NULL) {
+ INSIST(atomic_load(&sock->client));
+ isc_tlsctx_client_session_cache_detach(
+ &sock->tlsstream.client_sess_cache);
+ }
+ } else if (sock->type == isc_nm_tcpsocket &&
+ sock->tlsstream.tlssocket != NULL)
+ {
+ /*
+ * The TLS socket can't be destroyed until its underlying TCP
+ * socket is, to avoid possible use-after-free errors.
+ */
+ isc__nmsocket_detach(&sock->tlsstream.tlssocket);
+ }
+}
+
+void
+isc__nm_tls_cleartimeout(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+ sock = handle->sock;
+ if (sock->outerhandle != NULL) {
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+ isc_nmhandle_cleartimeout(sock->outerhandle);
+ }
+}
+
+void
+isc__nm_tls_settimeout(isc_nmhandle_t *handle, uint32_t timeout) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+ sock = handle->sock;
+ if (sock->outerhandle != NULL) {
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+ isc_nmhandle_settimeout(sock->outerhandle, timeout);
+ }
+}
+
+void
+isc__nmhandle_tls_keepalive(isc_nmhandle_t *handle, bool value) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+ sock = handle->sock;
+ if (sock->outerhandle != NULL) {
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+
+ isc_nmhandle_keepalive(sock->outerhandle, value);
+ }
+}
+
+void
+isc__nmhandle_tls_setwritetimeout(isc_nmhandle_t *handle,
+ uint64_t write_timeout) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+ sock = handle->sock;
+ if (sock->outerhandle != NULL) {
+ INSIST(VALID_NMHANDLE(sock->outerhandle));
+
+ isc_nmhandle_setwritetimeout(sock->outerhandle, write_timeout);
+ }
+}
+
+const char *
+isc__nm_tls_verify_tls_peer_result_string(const isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+ REQUIRE(handle->sock->type == isc_nm_tlssocket);
+
+ sock = handle->sock;
+ if (sock->tlsstream.tls == NULL) {
+ return (NULL);
+ }
+
+ return (isc_tls_verify_peer_result_string(sock->tlsstream.tls));
+}
+
+static void
+tls_init_listener_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *ctx) {
+ size_t nworkers;
+
+ REQUIRE(VALID_NM(listener->mgr));
+ REQUIRE(ctx != NULL);
+
+ nworkers = (size_t)listener->mgr->nworkers;
+ INSIST(nworkers > 0);
+
+ listener->tlsstream.listener_tls_ctx = isc_mem_get(
+ listener->mgr->mctx, sizeof(isc_tlsctx_t *) * nworkers);
+ listener->tlsstream.n_listener_tls_ctx = nworkers;
+ for (size_t i = 0; i < nworkers; i++) {
+ listener->tlsstream.listener_tls_ctx[i] = NULL;
+ isc_tlsctx_attach(ctx,
+ &listener->tlsstream.listener_tls_ctx[i]);
+ }
+}
+
+static void
+tls_cleanup_listener_tlsctx(isc_nmsocket_t *listener) {
+ REQUIRE(VALID_NM(listener->mgr));
+
+ if (listener->tlsstream.listener_tls_ctx == NULL) {
+ return;
+ }
+
+ for (size_t i = 0; i < listener->tlsstream.n_listener_tls_ctx; i++) {
+ isc_tlsctx_free(&listener->tlsstream.listener_tls_ctx[i]);
+ }
+ isc_mem_put(listener->mgr->mctx, listener->tlsstream.listener_tls_ctx,
+ sizeof(isc_tlsctx_t *) *
+ listener->tlsstream.n_listener_tls_ctx);
+ listener->tlsstream.n_listener_tls_ctx = 0;
+}
+
+static isc_tlsctx_t *
+tls_get_listener_tlsctx(isc_nmsocket_t *listener, const int tid) {
+ REQUIRE(VALID_NM(listener->mgr));
+ REQUIRE(tid >= 0);
+
+ if (listener->tlsstream.listener_tls_ctx == NULL) {
+ return (NULL);
+ }
+
+ return (listener->tlsstream.listener_tls_ctx[tid]);
+}
+
+void
+isc__nm_async_tls_set_tlsctx(isc_nmsocket_t *listener, isc_tlsctx_t *tlsctx,
+ const int tid) {
+ REQUIRE(tid >= 0);
+
+ isc_tlsctx_free(&listener->tlsstream.listener_tls_ctx[tid]);
+ isc_tlsctx_attach(tlsctx, &listener->tlsstream.listener_tls_ctx[tid]);
+}
+
+static void
+tls_keep_client_tls_session(isc_nmsocket_t *sock) {
+ /*
+ * Ensure that the isc_tls_t is being accessed from
+ * within the worker thread the socket is bound to.
+ */
+ REQUIRE(sock->tid == isc_nm_tid());
+ if (sock->tlsstream.client_sess_cache != NULL &&
+ sock->tlsstream.client_session_saved == false)
+ {
+ INSIST(atomic_load(&sock->client));
+ isc_tlsctx_client_session_cache_keep_sockaddr(
+ sock->tlsstream.client_sess_cache, &sock->peer,
+ sock->tlsstream.tls);
+ sock->tlsstream.client_session_saved = true;
+ }
+}
+
+static void
+tls_try_shutdown(isc_tls_t *tls, const bool force) {
+ if (force) {
+ (void)SSL_set_shutdown(tls, SSL_SENT_SHUTDOWN);
+ } else if ((SSL_get_shutdown(tls) & SSL_SENT_SHUTDOWN) == 0) {
+ (void)SSL_shutdown(tls);
+ }
+}
diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c
new file mode 100644
index 0000000..1a0ee16
--- /dev/null
+++ b/lib/isc/netmgr/udp.c
@@ -0,0 +1,1405 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <unistd.h>
+#include <uv.h>
+
+#include <isc/atomic.h>
+#include <isc/barrier.h>
+#include <isc/buffer.h>
+#include <isc/condition.h>
+#include <isc/errno.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/sockaddr.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+#include "uv-compat.h"
+
+#ifdef HAVE_NET_ROUTE_H
+#include <net/route.h>
+#if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define USE_ROUTE_SOCKET 1
+#define ROUTE_SOCKET_PF PF_ROUTE
+#define ROUTE_SOCKET_PROTOCOL 0
+#define MSGHDR rt_msghdr
+#define MSGTYPE rtm_type
+#endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \
+ * defined(RTM_DELADDR) */
+#endif /* ifdef HAVE_NET_ROUTE_H */
+
+#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define USE_ROUTE_SOCKET 1
+#define USE_NETLINK 1
+#define ROUTE_SOCKET_PF PF_NETLINK
+#define ROUTE_SOCKET_PROTOCOL NETLINK_ROUTE
+#define MSGHDR nlmsghdr
+#define MSGTYPE nlmsg_type
+#endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */
+#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \
+ */
+
+static isc_result_t
+udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_sockaddr_t *peer);
+
+static void
+udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+ const struct sockaddr *addr, unsigned flags);
+
+static void
+udp_send_cb(uv_udp_send_t *req, int status);
+
+static void
+udp_close_cb(uv_handle_t *handle);
+
+static void
+read_timer_close_cb(uv_handle_t *handle);
+
+static void
+udp_close_direct(isc_nmsocket_t *sock);
+
+static void
+stop_udp_parent(isc_nmsocket_t *sock);
+static void
+stop_udp_child(isc_nmsocket_t *sock);
+
+static uv_os_sock_t
+isc__nm_udp_lb_socket(isc_nm_t *mgr, sa_family_t sa_family) {
+ isc_result_t result;
+ uv_os_sock_t sock;
+
+ result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ (void)isc__nm_socket_incoming_cpu(sock);
+ (void)isc__nm_socket_disable_pmtud(sock, sa_family);
+ (void)isc__nm_socket_v6only(sock, sa_family);
+
+ result = isc__nm_socket_reuse(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ if (mgr->load_balance_sockets) {
+ result = isc__nm_socket_reuse_lb(sock);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ return (sock);
+}
+
+static void
+start_udp_child(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nmsocket_t *sock,
+ uv_os_sock_t fd, int tid) {
+ isc_nmsocket_t *csock;
+ isc__netievent_udplisten_t *ievent = NULL;
+
+ csock = &sock->children[tid];
+
+ isc__nmsocket_init(csock, mgr, isc_nm_udpsocket, iface);
+ csock->parent = sock;
+ csock->iface = sock->iface;
+ atomic_init(&csock->reading, true);
+ csock->recv_cb = sock->recv_cb;
+ csock->recv_cbarg = sock->recv_cbarg;
+ csock->extrahandlesize = sock->extrahandlesize;
+ csock->tid = tid;
+
+ if (mgr->load_balance_sockets) {
+ UNUSED(fd);
+ csock->fd = isc__nm_udp_lb_socket(mgr,
+ iface->type.sa.sa_family);
+ } else {
+ csock->fd = dup(fd);
+ }
+ REQUIRE(csock->fd >= 0);
+
+ ievent = isc__nm_get_netievent_udplisten(mgr, csock);
+ isc__nm_maybe_enqueue_ievent(&mgr->workers[tid],
+ (isc__netievent_t *)ievent);
+}
+
+static void
+enqueue_stoplistening(isc_nmsocket_t *sock) {
+ isc__netievent_udpstop_t *ievent =
+ isc__nm_get_netievent_udpstop(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+isc_result_t
+isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb,
+ void *cbarg, size_t extrahandlesize, isc_nmsocket_t **sockp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ size_t children_size = 0;
+ REQUIRE(VALID_NM(mgr));
+ uv_os_sock_t fd = -1;
+
+ /*
+ * We are creating mgr->nworkers duplicated sockets, one
+ * socket for each worker thread.
+ */
+ sock = isc_mem_get(mgr->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(sock, mgr, isc_nm_udplistener, iface);
+
+ atomic_init(&sock->rchildren, 0);
+ sock->nchildren = mgr->nworkers;
+ children_size = sock->nchildren * sizeof(sock->children[0]);
+ sock->children = isc_mem_get(mgr->mctx, children_size);
+ memset(sock->children, 0, children_size);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->extrahandlesize = extrahandlesize;
+ sock->result = ISC_R_UNSET;
+
+ sock->tid = 0;
+ sock->fd = -1;
+
+ if (!mgr->load_balance_sockets) {
+ fd = isc__nm_udp_lb_socket(mgr, iface->type.sa.sa_family);
+ }
+
+ isc_barrier_init(&sock->startlistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ if ((int)i == isc_nm_tid()) {
+ continue;
+ }
+ start_udp_child(mgr, iface, sock, fd, i);
+ }
+
+ if (isc__nm_in_netthread()) {
+ start_udp_child(mgr, iface, sock, fd, isc_nm_tid());
+ }
+
+ if (!mgr->load_balance_sockets) {
+ isc__nm_closesocket(fd);
+ }
+
+ LOCK(&sock->lock);
+ while (atomic_load(&sock->rchildren) != sock->nchildren) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ result = sock->result;
+ atomic_store(&sock->active, true);
+ UNLOCK(&sock->lock);
+
+ INSIST(result != ISC_R_UNSET);
+
+ if (result == ISC_R_SUCCESS) {
+ REQUIRE(atomic_load(&sock->rchildren) == sock->nchildren);
+ *sockp = sock;
+ } else {
+ atomic_store(&sock->active, false);
+ enqueue_stoplistening(sock);
+ isc_nmsocket_close(&sock);
+ }
+
+ return (result);
+}
+
+#ifdef USE_ROUTE_SOCKET
+static isc_result_t
+route_socket(uv_os_sock_t *fdp) {
+ isc_result_t result;
+ uv_os_sock_t fd;
+#ifdef USE_NETLINK
+ struct sockaddr_nl sa;
+ int r;
+#endif
+
+ result = isc__nm_socket(ROUTE_SOCKET_PF, SOCK_RAW,
+ ROUTE_SOCKET_PROTOCOL, &fd);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+#ifdef USE_NETLINK
+ sa.nl_family = PF_NETLINK;
+ sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+ r = bind(fd, (struct sockaddr *)&sa, sizeof(sa));
+ if (r < 0) {
+ isc__nm_closesocket(fd);
+ return (isc_errno_toresult(r));
+ }
+#endif
+
+ *fdp = fd;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+route_connect_direct(isc_nmsocket_t *sock) {
+ isc__networker_t *worker = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ int r;
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ worker = &sock->mgr->workers[isc_nm_tid()];
+
+ atomic_store(&sock->connecting, true);
+
+ r = uv_udp_init(&worker->loop, &sock->uv_handle.udp);
+ UV_RUNTIME_CHECK(uv_udp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ }
+
+ r = uv_udp_open(&sock->uv_handle.udp, sock->fd);
+ if (r != 0) {
+ goto done;
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ atomic_store(&sock->connecting, false);
+ atomic_store(&sock->connected, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+error:
+
+ LOCK(&sock->lock);
+ sock->result = result;
+ SIGNAL(&sock->cond);
+ if (!atomic_load(&sock->active)) {
+ WAIT(&sock->scond, &sock->lock);
+ }
+ INSIST(atomic_load(&sock->active));
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+/*
+ * Asynchronous 'udpconnect' call handler: open a new UDP socket and
+ * call the 'open' callback with a handle.
+ */
+void
+isc__nm_async_routeconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_routeconnect_t *ievent =
+ (isc__netievent_routeconnect_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->parent == NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = route_connect_direct(sock);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->active, false);
+ isc__nm_udp_close(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ } else {
+ /*
+ * The callback has to be called after the socket has been
+ * initialized
+ */
+ isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true);
+ }
+
+ /*
+ * The sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&sock);
+}
+#endif /* USE_ROUTE_SOCKET */
+
+isc_result_t
+isc_nm_routeconnect(isc_nm_t *mgr, isc_nm_cb_t cb, void *cbarg,
+ size_t extrahandlesize) {
+#ifdef USE_ROUTE_SOCKET
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_udpconnect_t *event = NULL;
+ isc__nm_uvreq_t *req = NULL;
+
+ REQUIRE(VALID_NM(mgr));
+
+ sock = isc_mem_get(mgr->mctx, sizeof(*sock));
+ isc__nmsocket_init(sock, mgr, isc_nm_udpsocket, NULL);
+
+ sock->connect_cb = cb;
+ sock->connect_cbarg = cbarg;
+ sock->extrahandlesize = extrahandlesize;
+ sock->result = ISC_R_UNSET;
+ atomic_init(&sock->client, true);
+ sock->route_sock = true;
+
+ req = isc__nm_uvreq_get(mgr, sock);
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->handle = isc__nmhandle_get(sock, NULL, NULL);
+
+ result = route_socket(&sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ }
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+ return (result);
+ }
+
+ event = isc__nm_get_netievent_routeconnect(mgr, sock, req);
+
+ if (isc__nm_in_netthread()) {
+ atomic_store(&sock->active, true);
+ sock->tid = isc_nm_tid();
+ isc__nm_async_routeconnect(&mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ isc__nm_put_netievent_routeconnect(mgr, event);
+ } else {
+ atomic_init(&sock->active, false);
+ sock->tid = 0;
+ isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ }
+ LOCK(&sock->lock);
+ while (sock->result == ISC_R_UNSET) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ atomic_store(&sock->active, true);
+ BROADCAST(&sock->scond);
+ UNLOCK(&sock->lock);
+
+ return (sock->result);
+#else /* USE_ROUTE_SOCKET */
+ UNUSED(mgr);
+ UNUSED(cb);
+ UNUSED(cbarg);
+ UNUSED(extrahandlesize);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif /* USE_ROUTE_SOCKET */
+}
+
+/*
+ * Asynchronous 'udplisten' call handler: start listening on a UDP socket.
+ */
+void
+isc__nm_async_udplisten(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udplisten_t *ievent = (isc__netievent_udplisten_t *)ev0;
+ isc_nmsocket_t *sock = NULL;
+ int r, uv_bind_flags = 0;
+ int uv_init_flags = 0;
+ sa_family_t sa_family;
+ isc_result_t result = ISC_R_UNSET;
+ isc_nm_t *mgr = NULL;
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+ REQUIRE(ievent->sock->tid == isc_nm_tid());
+ REQUIRE(VALID_NMSOCK(ievent->sock->parent));
+
+ sock = ievent->sock;
+ sa_family = sock->iface.type.sa.sa_family;
+ mgr = sock->mgr;
+
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->parent != NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+
+#if HAVE_DECL_UV_UDP_RECVMMSG
+ uv_init_flags |= UV_UDP_RECVMMSG;
+#endif
+ r = uv_udp_init_ex(&worker->loop, &sock->uv_handle.udp, uv_init_flags);
+ UV_RUNTIME_CHECK(uv_udp_init_ex, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+ /* This keeps the socket alive after everything else is gone */
+ isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL });
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ LOCK(&sock->parent->lock);
+
+ r = uv_udp_open(&sock->uv_handle.udp, sock->fd);
+ if (r < 0) {
+ isc__nm_closesocket(sock->fd);
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (sa_family == AF_INET6) {
+ uv_bind_flags |= UV_UDP_IPV6ONLY;
+ }
+
+ if (mgr->load_balance_sockets) {
+ r = isc_uv_udp_freebind(&sock->uv_handle.udp,
+ &sock->parent->iface.type.sa,
+ uv_bind_flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ } else {
+ if (sock->parent->fd == -1) {
+ /* This thread is first, bind the socket */
+ r = isc_uv_udp_freebind(&sock->uv_handle.udp,
+ &sock->parent->iface.type.sa,
+ uv_bind_flags);
+ if (r < 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+ sock->parent->uv_handle.udp.flags =
+ sock->uv_handle.udp.flags;
+ sock->parent->fd = sock->fd;
+ } else {
+ /* The socket is already bound, just copy the flags */
+ sock->uv_handle.udp.flags =
+ sock->parent->uv_handle.udp.flags;
+ }
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb,
+ udp_recv_cb);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+
+ atomic_store(&sock->listening, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+ atomic_fetch_add(&sock->parent->rchildren, 1);
+ if (sock->parent->result == ISC_R_UNSET) {
+ sock->parent->result = result;
+ }
+ SIGNAL(&sock->parent->cond);
+ UNLOCK(&sock->parent->lock);
+
+ isc_barrier_wait(&sock->parent->startlistening);
+}
+
+void
+isc__nm_udp_stoplistening(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_udplistener);
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ if (!isc__nm_in_netthread()) {
+ enqueue_stoplistening(sock);
+ } else {
+ stop_udp_parent(sock);
+ }
+}
+
+/*
+ * Asynchronous 'udpstop' call handler: stop listening on a UDP socket.
+ */
+void
+isc__nm_async_udpstop(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udpstop_t *ievent = (isc__netievent_udpstop_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (sock->parent != NULL) {
+ stop_udp_child(sock);
+ return;
+ }
+
+ stop_udp_parent(sock);
+}
+
+/*
+ * udp_recv_cb handles incoming UDP packet from uv. The buffer here is
+ * reused for a series of packets, so we need to allocate a new one.
+ * This new one can be reused to send the response then.
+ */
+static void
+udp_recv_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+ const struct sockaddr *addr, unsigned flags) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
+ isc__nm_uvreq_t *req = NULL;
+ uint32_t maxudp;
+ isc_result_t result;
+ isc_sockaddr_t sockaddr, *sa = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->reading));
+
+ /*
+ * When using recvmmsg(2), if no errors occur, there will be a final
+ * callback with nrecv set to 0, addr set to NULL and the buffer
+ * pointing at the initially allocated data with the UV_UDP_MMSG_CHUNK
+ * flag cleared and the UV_UDP_MMSG_FREE flag set.
+ */
+#if HAVE_DECL_UV_UDP_MMSG_FREE
+ if ((flags & UV_UDP_MMSG_FREE) == UV_UDP_MMSG_FREE) {
+ INSIST(nrecv == 0);
+ INSIST(addr == NULL);
+ goto free;
+ }
+#else
+ UNUSED(flags);
+#endif
+
+ /*
+ * - If we're simulating a firewall blocking UDP packets
+ * bigger than 'maxudp' bytes for testing purposes.
+ */
+ maxudp = atomic_load(&sock->mgr->maxudp);
+ if ((maxudp != 0 && (uint32_t)nrecv > maxudp)) {
+ /*
+ * We need to keep the read_cb intact in case, so the
+ * readtimeout_cb can trigger and not crash because of
+ * missing read_req.
+ */
+ goto free;
+ }
+
+ /*
+ * - If there was a networking error.
+ */
+ if (nrecv < 0) {
+ isc__nm_failed_read_cb(sock, isc__nm_uverr2result(nrecv),
+ false);
+ goto free;
+ }
+
+ /*
+ * - If addr == NULL, in which case it's the end of stream;
+ * we can free the buffer and bail.
+ */
+ if (addr == NULL) {
+ isc__nm_failed_read_cb(sock, ISC_R_EOF, false);
+ goto free;
+ }
+
+ /*
+ * - If the socket is no longer active.
+ */
+ if (!isc__nmsocket_active(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ goto free;
+ }
+
+ if (!sock->route_sock) {
+ result = isc_sockaddr_fromsockaddr(&sockaddr, addr);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ sa = &sockaddr;
+ }
+
+ req = isc__nm_get_read_req(sock, sa);
+
+ /*
+ * The callback will be called synchronously, because result is
+ * ISC_R_SUCCESS, so we are ok of passing the buf directly.
+ */
+ req->uvbuf.base = buf->base;
+ req->uvbuf.len = nrecv;
+
+ sock->recv_read = false;
+
+ REQUIRE(!sock->processing);
+ sock->processing = true;
+ isc__nm_readcb(sock, req, ISC_R_SUCCESS);
+ sock->processing = false;
+
+free:
+#if HAVE_DECL_UV_UDP_MMSG_CHUNK
+ /*
+ * When using recvmmsg(2), chunks will have the UV_UDP_MMSG_CHUNK flag
+ * set, those must not be freed.
+ */
+ if ((flags & UV_UDP_MMSG_CHUNK) == UV_UDP_MMSG_CHUNK) {
+ return;
+ }
+#endif
+
+ /*
+ * When using recvmmsg(2), if a UDP socket error occurs, nrecv will be <
+ * 0. In either scenario, the callee can now safely free the provided
+ * buffer.
+ */
+ if (nrecv < 0) {
+ /*
+ * The buffer may be a null buffer on error.
+ */
+ if (buf->base == NULL && buf->len == 0) {
+ return;
+ }
+ }
+
+ isc__nm_free_uvbuf(sock, buf);
+}
+
+/*
+ * Send the data in 'region' to a peer via a UDP socket. We try to find
+ * a proper sibling/child socket so that we won't have to jump to
+ * another thread.
+ */
+void
+isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region,
+ isc_nm_cb_t cb, void *cbarg) {
+ isc_nmsocket_t *sock = handle->sock;
+ isc_nmsocket_t *rsock = NULL;
+ isc_sockaddr_t *peer = &handle->peer;
+ isc__nm_uvreq_t *uvreq = NULL;
+ uint32_t maxudp = atomic_load(&sock->mgr->maxudp);
+ int ntid;
+
+ INSIST(sock->type == isc_nm_udpsocket);
+
+ /*
+ * We're simulating a firewall blocking UDP packets bigger than
+ * 'maxudp' bytes, for testing purposes.
+ *
+ * The client would ordinarily have unreferenced the handle
+ * in the callback, but that won't happen in this case, so
+ * we need to do so here.
+ */
+ if (maxudp != 0 && region->length > maxudp) {
+ isc_nmhandle_detach(&handle);
+ return;
+ }
+
+ if (atomic_load(&sock->client)) {
+ /*
+ * When we are sending from the client socket, we directly use
+ * the socket provided.
+ */
+ rsock = sock;
+ goto send;
+ } else {
+ /*
+ * When we are sending from the server socket, we either use the
+ * socket associated with the network thread we are in, or we
+ * use the thread from the socket associated with the handle.
+ */
+ INSIST(sock->parent != NULL);
+
+ if (isc__nm_in_netthread()) {
+ ntid = isc_nm_tid();
+ } else {
+ ntid = sock->tid;
+ }
+ rsock = &sock->parent->children[ntid];
+ }
+
+send:
+ uvreq = isc__nm_uvreq_get(rsock->mgr, rsock);
+ uvreq->uvbuf.base = (char *)region->base;
+ uvreq->uvbuf.len = region->length;
+
+ isc_nmhandle_attach(handle, &uvreq->handle);
+
+ uvreq->cb.send = cb;
+ uvreq->cbarg = cbarg;
+
+ if (isc_nm_tid() == rsock->tid) {
+ REQUIRE(rsock->tid == isc_nm_tid());
+ isc__netievent_udpsend_t ievent = { .sock = rsock,
+ .req = uvreq,
+ .peer = *peer };
+
+ isc__nm_async_udpsend(NULL, (isc__netievent_t *)&ievent);
+ } else {
+ isc__netievent_udpsend_t *ievent =
+ isc__nm_get_netievent_udpsend(sock->mgr, rsock);
+ ievent->peer = *peer;
+ ievent->req = uvreq;
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[rsock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+/*
+ * Asynchronous 'udpsend' event handler: send a packet on a UDP socket.
+ */
+void
+isc__nm_async_udpsend(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc_result_t result;
+ isc__netievent_udpsend_t *ievent = (isc__netievent_udpsend_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *uvreq = ievent->req;
+
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ if (isc__nmsocket_closing(sock)) {
+ isc__nm_failed_send_cb(sock, uvreq, ISC_R_CANCELED);
+ return;
+ }
+
+ result = udp_send_direct(sock, uvreq, &ievent->peer);
+ if (result != ISC_R_SUCCESS) {
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ isc__nm_failed_send_cb(sock, uvreq, result);
+ }
+}
+
+static void
+udp_send_cb(uv_udp_send_t *req, int status) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc__nm_uvreq_t *uvreq = uv_handle_get_data((uv_handle_t *)req);
+ isc_nmsocket_t *sock = NULL;
+
+ REQUIRE(VALID_UVREQ(uvreq));
+ REQUIRE(VALID_NMHANDLE(uvreq->handle));
+
+ sock = uvreq->sock;
+
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (status < 0) {
+ result = isc__nm_uverr2result(status);
+ isc__nm_incstats(sock, STATID_SENDFAIL);
+ }
+
+ isc__nm_sendcb(sock, uvreq, result, false);
+}
+
+/*
+ * udp_send_direct sends buf to a peer on a socket. Sock has to be in
+ * the same thread as the callee.
+ */
+static isc_result_t
+udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
+ isc_sockaddr_t *peer) {
+ const struct sockaddr *sa = &peer->type.sa;
+ int r;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(VALID_UVREQ(req));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_udpsocket);
+
+ if (isc__nmsocket_closing(sock)) {
+ return (ISC_R_CANCELED);
+ }
+
+#if UV_VERSION_HEX >= UV_VERSION(1, 27, 0)
+ /*
+ * If we used uv_udp_connect() (and not the shim version for
+ * older versions of libuv), then the peer address has to be
+ * set to NULL or else uv_udp_send() could fail or assert,
+ * depending on the libuv version.
+ */
+ if (atomic_load(&sock->connected)) {
+ sa = NULL;
+ }
+#endif
+
+ r = uv_udp_send(&req->uv_req.udp_send, &sock->uv_handle.udp,
+ &req->uvbuf, 1, sa, udp_send_cb);
+ if (r < 0) {
+ return (isc__nm_uverr2result(r));
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+udp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) {
+ isc__networker_t *worker = NULL;
+ int uv_bind_flags = UV_UDP_REUSEADDR;
+ isc_result_t result = ISC_R_UNSET;
+ int r;
+
+ REQUIRE(isc__nm_in_netthread());
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ worker = &sock->mgr->workers[isc_nm_tid()];
+
+ atomic_store(&sock->connecting, true);
+
+ r = uv_udp_init(&worker->loop, &sock->uv_handle.udp);
+ UV_RUNTIME_CHECK(uv_udp_init, r);
+ uv_handle_set_data(&sock->uv_handle.handle, sock);
+
+ r = uv_timer_init(&worker->loop, &sock->read_timer);
+ UV_RUNTIME_CHECK(uv_timer_init, r);
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ goto error;
+ }
+
+ r = uv_udp_open(&sock->uv_handle.udp, sock->fd);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_OPENFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_OPEN);
+
+ if (sock->iface.type.sa.sa_family == AF_INET6) {
+ uv_bind_flags |= UV_UDP_IPV6ONLY;
+ }
+
+ r = uv_udp_bind(&sock->uv_handle.udp, &sock->iface.type.sa,
+ uv_bind_flags);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_BINDFAIL);
+ goto done;
+ }
+
+ isc__nm_set_network_buffers(sock->mgr, &sock->uv_handle.handle);
+
+ /*
+ * On FreeBSD the UDP connect() call sometimes results in a
+ * spurious transient EADDRINUSE. Try a few more times before
+ * giving up.
+ */
+ do {
+ r = isc_uv_udp_connect(&sock->uv_handle.udp,
+ &req->peer.type.sa);
+ } while (r == UV_EADDRINUSE && --req->connect_tries > 0);
+ if (r != 0) {
+ isc__nm_incstats(sock, STATID_CONNECTFAIL);
+ goto done;
+ }
+ isc__nm_incstats(sock, STATID_CONNECT);
+
+ atomic_store(&sock->connecting, false);
+ atomic_store(&sock->connected, true);
+
+done:
+ result = isc__nm_uverr2result(r);
+error:
+
+ LOCK(&sock->lock);
+ sock->result = result;
+ SIGNAL(&sock->cond);
+ if (!atomic_load(&sock->active)) {
+ WAIT(&sock->scond, &sock->lock);
+ }
+ INSIST(atomic_load(&sock->active));
+ UNLOCK(&sock->lock);
+
+ return (result);
+}
+
+/*
+ * Asynchronous 'udpconnect' call handler: open a new UDP socket and
+ * call the 'open' callback with a handle.
+ */
+void
+isc__nm_async_udpconnect(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udpconnect_t *ievent =
+ (isc__netievent_udpconnect_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc__nm_uvreq_t *req = ievent->req;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->parent == NULL);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ result = udp_connect_direct(sock, req);
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->active, false);
+ isc__nm_udp_close(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ } else {
+ /*
+ * The callback has to be called after the socket has been
+ * initialized
+ */
+ isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true);
+ }
+
+ /*
+ * The sock is now attached to the handle.
+ */
+ isc__nmsocket_detach(&sock);
+}
+
+void
+isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
+ isc_nm_cb_t cb, void *cbarg, unsigned int timeout,
+ size_t extrahandlesize) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_udpconnect_t *event = NULL;
+ isc__nm_uvreq_t *req = NULL;
+ sa_family_t sa_family;
+
+ REQUIRE(VALID_NM(mgr));
+ REQUIRE(local != NULL);
+ REQUIRE(peer != NULL);
+
+ sa_family = peer->type.sa.sa_family;
+
+ sock = isc_mem_get(mgr->mctx, sizeof(isc_nmsocket_t));
+ isc__nmsocket_init(sock, mgr, isc_nm_udpsocket, local);
+
+ sock->connect_cb = cb;
+ sock->connect_cbarg = cbarg;
+ sock->read_timeout = timeout;
+ sock->extrahandlesize = extrahandlesize;
+ sock->peer = *peer;
+ sock->result = ISC_R_UNSET;
+ atomic_init(&sock->client, true);
+
+ req = isc__nm_uvreq_get(mgr, sock);
+ req->cb.connect = cb;
+ req->cbarg = cbarg;
+ req->peer = *peer;
+ req->local = *local;
+ req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface);
+
+ result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &sock->fd);
+ if (result != ISC_R_SUCCESS) {
+ if (isc__nm_in_netthread()) {
+ sock->tid = isc_nm_tid();
+ }
+ isc__nmsocket_clearcb(sock);
+ isc__nm_connectcb(sock, req, result, true);
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_detach(&sock);
+ return;
+ }
+
+ result = isc__nm_socket_reuse(sock->fd);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS ||
+ result == ISC_R_NOTIMPLEMENTED);
+
+ result = isc__nm_socket_reuse_lb(sock->fd);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS ||
+ result == ISC_R_NOTIMPLEMENTED);
+
+ (void)isc__nm_socket_incoming_cpu(sock->fd);
+
+ (void)isc__nm_socket_disable_pmtud(sock->fd, sa_family);
+
+ (void)isc__nm_socket_min_mtu(sock->fd, sa_family);
+
+ event = isc__nm_get_netievent_udpconnect(mgr, sock, req);
+
+ if (isc__nm_in_netthread()) {
+ atomic_store(&sock->active, true);
+ sock->tid = isc_nm_tid();
+ isc__nm_async_udpconnect(&mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ isc__nm_put_netievent_udpconnect(mgr, event);
+ } else {
+ atomic_init(&sock->active, false);
+ sock->tid = isc_random_uniform(mgr->nworkers);
+ isc__nm_enqueue_ievent(&mgr->workers[sock->tid],
+ (isc__netievent_t *)event);
+ }
+ LOCK(&sock->lock);
+ while (sock->result == ISC_R_UNSET) {
+ WAIT(&sock->cond, &sock->lock);
+ }
+ atomic_store(&sock->active, true);
+ BROADCAST(&sock->scond);
+ UNLOCK(&sock->lock);
+}
+
+void
+isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf,
+ const struct sockaddr *addr, unsigned flags) {
+ isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle);
+ REQUIRE(VALID_NMSOCK(sock));
+
+ udp_recv_cb(handle, nrecv, buf, addr, flags);
+ /*
+ * If a caller calls isc_nm_read() on a listening socket, we can
+ * get here, but we MUST NOT stop reading from the listener
+ * socket. The only difference between listener and connected
+ * sockets is that the former has sock->parent set and later
+ * does not.
+ */
+ if (!sock->parent) {
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+ }
+}
+
+void
+isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(result != ISC_R_SUCCESS);
+
+ if (atomic_load(&sock->client)) {
+ isc__nmsocket_timer_stop(sock);
+ isc__nm_stop_reading(sock);
+
+ if (!sock->recv_read) {
+ goto destroy;
+ }
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nmsocket_clearcb(sock);
+ isc__nm_readcb(sock, req, result);
+ }
+
+ destroy:
+ isc__nmsocket_prep_destroy(sock);
+ return;
+ }
+
+ /*
+ * For UDP server socket, we don't have child socket via
+ * "accept", so we:
+ * - we continue to read
+ * - we don't clear the callbacks
+ * - we don't destroy it (only stoplistening could do that)
+ */
+ if (!sock->recv_read) {
+ return;
+ }
+ sock->recv_read = false;
+
+ if (sock->recv_cb != NULL) {
+ isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL);
+ isc__nm_readcb(sock, req, result);
+ }
+}
+
+/*
+ * Asynchronous 'udpread' call handler: start or resume reading on a
+ * socket; pause reading and call the 'recv' callback after each
+ * datagram.
+ */
+void
+isc__nm_async_udpread(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udpread_t *ievent = (isc__netievent_udpread_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+ isc_result_t result;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (isc__nm_closing(sock)) {
+ result = ISC_R_SHUTTINGDOWN;
+ } else if (isc__nmsocket_closing(sock)) {
+ result = ISC_R_CANCELED;
+ } else {
+ result = isc__nm_start_reading(sock);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ atomic_store(&sock->reading, true);
+ isc__nm_failed_read_cb(sock, result, false);
+ return;
+ }
+
+ isc__nmsocket_timer_start(sock);
+}
+
+void
+isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_NMHANDLE(handle));
+ REQUIRE(VALID_NMSOCK(handle->sock));
+
+ isc_nmsocket_t *sock = handle->sock;
+
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->statichandle == handle);
+ REQUIRE(!sock->recv_read);
+
+ sock->recv_cb = cb;
+ sock->recv_cbarg = cbarg;
+ sock->recv_read = true;
+
+ if (!atomic_load(&sock->reading) && sock->tid == isc_nm_tid()) {
+ isc__netievent_udpread_t ievent = { .sock = sock };
+ isc__nm_async_udpread(NULL, (isc__netievent_t *)&ievent);
+ } else {
+ isc__netievent_udpread_t *ievent =
+ isc__nm_get_netievent_udpread(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+static void
+udp_stop_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ atomic_store(&sock->listening, false);
+
+ isc__nmsocket_detach(&sock);
+}
+
+static void
+udp_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->closing));
+
+ if (!atomic_compare_exchange_strong(&sock->closed, &(bool){ false },
+ true))
+ {
+ UNREACHABLE();
+ }
+
+ isc__nm_incstats(sock, STATID_CLOSE);
+
+ if (sock->server != NULL) {
+ isc__nmsocket_detach(&sock->server);
+ }
+
+ atomic_store(&sock->connected, false);
+ atomic_store(&sock->listening, false);
+
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+read_timer_close_cb(uv_handle_t *handle) {
+ isc_nmsocket_t *sock = uv_handle_get_data(handle);
+ uv_handle_set_data(handle, NULL);
+
+ if (sock->parent) {
+ uv_close(&sock->uv_handle.handle, udp_stop_cb);
+ } else {
+ uv_close(&sock->uv_handle.handle, udp_close_cb);
+ }
+}
+
+static void
+stop_udp_child(isc_nmsocket_t *sock) {
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ udp_close_direct(sock);
+
+ atomic_fetch_sub(&sock->parent->rchildren, 1);
+
+ isc_barrier_wait(&sock->parent->stoplistening);
+}
+
+static void
+stop_udp_parent(isc_nmsocket_t *sock) {
+ isc_nmsocket_t *csock = NULL;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_udplistener);
+
+ isc_barrier_init(&sock->stoplistening, sock->nchildren);
+
+ for (size_t i = 0; i < sock->nchildren; i++) {
+ csock = &sock->children[i];
+ REQUIRE(VALID_NMSOCK(csock));
+
+ if ((int)i == isc_nm_tid()) {
+ /*
+ * We need to schedule closing the other sockets first
+ */
+ continue;
+ }
+
+ atomic_store(&csock->active, false);
+ enqueue_stoplistening(csock);
+ }
+
+ csock = &sock->children[isc_nm_tid()];
+ atomic_store(&csock->active, false);
+ stop_udp_child(csock);
+
+ atomic_store(&sock->closed, true);
+ isc__nmsocket_prep_destroy(sock);
+}
+
+static void
+udp_close_direct(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+
+ uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock);
+ uv_close((uv_handle_t *)&sock->read_timer, read_timer_close_cb);
+}
+
+void
+isc__nm_async_udpclose(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udpclose_t *ievent = (isc__netievent_udpclose_t *)ev0;
+ isc_nmsocket_t *sock = ievent->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ UNUSED(worker);
+
+ udp_close_direct(sock);
+}
+
+void
+isc__nm_udp_close(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_udpsocket);
+ REQUIRE(!isc__nmsocket_active(sock));
+
+ if (!atomic_compare_exchange_strong(&sock->closing, &(bool){ false },
+ true))
+ {
+ return;
+ }
+
+ if (sock->tid == isc_nm_tid()) {
+ udp_close_direct(sock);
+ } else {
+ isc__netievent_udpclose_t *ievent =
+ isc__nm_get_netievent_udpclose(sock->mgr, sock);
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+ }
+}
+
+void
+isc__nm_udp_shutdown(isc_nmsocket_t *sock) {
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(sock->type == isc_nm_udpsocket);
+
+ /*
+ * If the socket is active, mark it inactive and
+ * continue. If it isn't active, stop now.
+ */
+ if (!isc__nmsocket_deactivate(sock)) {
+ return;
+ }
+
+ /*
+ * If the socket is connecting, the cancel will happen in the
+ * async_udpconnect() due socket being inactive now.
+ */
+ if (atomic_load(&sock->connecting)) {
+ return;
+ }
+
+ /*
+ * When the client detaches the last handle, the
+ * sock->statichandle would be NULL, in that case, nobody is
+ * interested in the callback.
+ */
+ if (sock->statichandle != NULL) {
+ if (isc__nm_closing(sock)) {
+ isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false);
+ } else {
+ isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false);
+ }
+ return;
+ }
+
+ /*
+ * Otherwise, we just send the socket to abyss...
+ */
+ if (sock->parent == NULL) {
+ isc__nmsocket_prep_destroy(sock);
+ }
+}
+
+void
+isc__nm_udp_cancelread(isc_nmhandle_t *handle) {
+ isc_nmsocket_t *sock = NULL;
+ isc__netievent_udpcancel_t *ievent = NULL;
+
+ REQUIRE(VALID_NMHANDLE(handle));
+
+ sock = handle->sock;
+
+ REQUIRE(VALID_NMSOCK(sock));
+ REQUIRE(sock->type == isc_nm_udpsocket);
+
+ ievent = isc__nm_get_netievent_udpcancel(sock->mgr, sock, handle);
+
+ isc__nm_enqueue_ievent(&sock->mgr->workers[sock->tid],
+ (isc__netievent_t *)ievent);
+}
+
+void
+isc__nm_async_udpcancel(isc__networker_t *worker, isc__netievent_t *ev0) {
+ isc__netievent_udpcancel_t *ievent = (isc__netievent_udpcancel_t *)ev0;
+ isc_nmsocket_t *sock = NULL;
+
+ UNUSED(worker);
+
+ REQUIRE(VALID_NMSOCK(ievent->sock));
+
+ sock = ievent->sock;
+
+ REQUIRE(sock->tid == isc_nm_tid());
+ REQUIRE(atomic_load(&sock->client));
+
+ isc__nm_failed_read_cb(sock, ISC_R_EOF, false);
+}
diff --git a/lib/isc/netmgr/uv-compat.c b/lib/isc/netmgr/uv-compat.c
new file mode 100644
index 0000000..b7c0f7b
--- /dev/null
+++ b/lib/isc/netmgr/uv-compat.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 "uv-compat.h"
+#include <unistd.h>
+
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+
+#if UV_VERSION_HEX < UV_VERSION(1, 27, 0)
+int
+isc_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr) {
+ int err = 0;
+
+ do {
+ int addrlen = (addr->sa_family == AF_INET)
+ ? sizeof(struct sockaddr_in)
+ : sizeof(struct sockaddr_in6);
+ err = connect(handle->io_watcher.fd, addr, addrlen);
+ } while (err == -1 && errno == EINTR);
+
+ if (err) {
+#if UV_VERSION_HEX >= UV_VERSION(1, 10, 0)
+ return (uv_translate_sys_error(errno));
+#else
+ return (-errno);
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 10, 0) */
+ }
+
+ return (0);
+}
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */
+
+#if UV_VERSION_HEX < UV_VERSION(1, 32, 0)
+int
+uv_tcp_close_reset(uv_tcp_t *handle, uv_close_cb close_cb) {
+ if (setsockopt(handle->io_watcher.fd, SOL_SOCKET, SO_LINGER,
+ &(struct linger){ 1, 0 }, sizeof(struct linger)) == -1)
+ {
+#if UV_VERSION_HEX >= UV_VERSION(1, 10, 0)
+ return (uv_translate_sys_error(errno));
+#else
+ return (-errno);
+#endif /* UV_VERSION_HEX >= UV_VERSION(1, 10, 0) */
+ }
+
+ uv_close((uv_handle_t *)handle, close_cb);
+ return (0);
+}
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 32, 0) */
+
+int
+isc_uv_udp_freebind(uv_udp_t *handle, const struct sockaddr *addr,
+ unsigned int flags) {
+ int r;
+ uv_os_sock_t fd;
+
+ r = uv_fileno((const uv_handle_t *)handle, (uv_os_fd_t *)&fd);
+ if (r < 0) {
+ return (r);
+ }
+
+ r = uv_udp_bind(handle, addr, flags);
+ if (r == UV_EADDRNOTAVAIL &&
+ isc__nm_socket_freebind(fd, addr->sa_family) == ISC_R_SUCCESS)
+ {
+ /*
+ * Retry binding with IP_FREEBIND (or equivalent option) if the
+ * address is not available. This helps with IPv6 tentative
+ * addresses which are reported by the route socket, although
+ * named is not yet able to properly bind to them.
+ */
+ r = uv_udp_bind(handle, addr, flags);
+ }
+
+ return (r);
+}
+
+static int
+isc__uv_tcp_bind_now(uv_tcp_t *handle, const struct sockaddr *addr,
+ unsigned int flags) {
+ int r;
+ struct sockaddr_storage sname;
+ int snamelen = sizeof(sname);
+
+ r = uv_tcp_bind(handle, addr, flags);
+ if (r < 0) {
+ return (r);
+ }
+
+ /*
+ * uv_tcp_bind() uses a delayed error, initially returning
+ * success even if bind() fails. By calling uv_tcp_getsockname()
+ * here we can find out whether the bind() call was successful.
+ */
+ r = uv_tcp_getsockname(handle, (struct sockaddr *)&sname, &snamelen);
+ if (r < 0) {
+ return (r);
+ }
+
+ return (0);
+}
+
+int
+isc_uv_tcp_freebind(uv_tcp_t *handle, const struct sockaddr *addr,
+ unsigned int flags) {
+ int r;
+ uv_os_sock_t fd;
+
+ r = uv_fileno((const uv_handle_t *)handle, (uv_os_fd_t *)&fd);
+ if (r < 0) {
+ return (r);
+ }
+
+ r = isc__uv_tcp_bind_now(handle, addr, flags);
+ if (r == UV_EADDRNOTAVAIL &&
+ isc__nm_socket_freebind(fd, addr->sa_family) == ISC_R_SUCCESS)
+ {
+ /*
+ * Retry binding with IP_FREEBIND (or equivalent option) if the
+ * address is not available. This helps with IPv6 tentative
+ * addresses which are reported by the route socket, although
+ * named is not yet able to properly bind to them.
+ */
+ r = isc__uv_tcp_bind_now(handle, addr, flags);
+ }
+
+ return (r);
+}
diff --git a/lib/isc/netmgr/uv-compat.h b/lib/isc/netmgr/uv-compat.h
new file mode 100644
index 0000000..3a10387
--- /dev/null
+++ b/lib/isc/netmgr/uv-compat.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
+#include <uv.h>
+
+/*
+ * These functions were introduced in newer libuv, but we still
+ * want BIND9 compile on older ones so we emulate them.
+ * They're inline to avoid conflicts when running with a newer
+ * library version.
+ */
+
+#define UV_VERSION(major, minor, patch) ((major << 16) | (minor << 8) | (patch))
+
+/*
+ * Copied verbatim from libuv/src/version.c
+ */
+
+#define UV_STRINGIFY(v) UV_STRINGIFY_HELPER(v)
+#define UV_STRINGIFY_HELPER(v) #v
+
+#define UV_VERSION_STRING_BASE \
+ UV_STRINGIFY(UV_VERSION_MAJOR) \
+ "." UV_STRINGIFY(UV_VERSION_MINOR) "." UV_STRINGIFY(UV_VERSION_PATCH)
+
+#if UV_VERSION_IS_RELEASE
+#define UV_VERSION_STRING UV_VERSION_STRING_BASE
+#else
+#define UV_VERSION_STRING UV_VERSION_STRING_BASE "-" UV_VERSION_SUFFIX
+#endif
+
+#if !defined(UV__ERR)
+#define UV__ERR(x) (-(x))
+#endif
+
+#if UV_VERSION_HEX < UV_VERSION(1, 19, 0)
+static inline void *
+uv_handle_get_data(const uv_handle_t *handle) {
+ return (handle->data);
+}
+
+static inline void
+uv_handle_set_data(uv_handle_t *handle, void *data) {
+ handle->data = data;
+}
+
+static inline void *
+uv_req_get_data(const uv_req_t *req) {
+ return (req->data);
+}
+
+static inline void
+uv_req_set_data(uv_req_t *req, void *data) {
+ req->data = data;
+}
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 19, 0) */
+
+#if UV_VERSION_HEX < UV_VERSION(1, 32, 0)
+int
+uv_tcp_close_reset(uv_tcp_t *handle, uv_close_cb close_cb);
+#endif
+
+#if UV_VERSION_HEX < UV_VERSION(1, 34, 0)
+#define uv_sleep(msec) usleep(msec * 1000)
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 34, 0) */
+
+#if UV_VERSION_HEX < UV_VERSION(1, 27, 0)
+int
+isc_uv_udp_connect(uv_udp_t *handle, const struct sockaddr *addr);
+/*%<
+ * Associate the UDP handle to a remote address and port, so every message sent
+ * by this handle is automatically sent to that destination.
+ *
+ * NOTE: This is just a limited shim for uv_udp_connect() as it requires the
+ * handle to be bound.
+ */
+#else /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */
+#define isc_uv_udp_connect uv_udp_connect
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 27, 0) */
+
+#if UV_VERSION_HEX < UV_VERSION(1, 12, 0)
+#include <stdlib.h>
+#include <string.h>
+
+static inline int
+uv_os_getenv(const char *name, char *buffer, size_t *size) {
+ size_t len;
+ char *buf = getenv(name);
+
+ if (buf == NULL) {
+ return (UV_ENOENT);
+ }
+
+ len = strlen(buf) + 1;
+ if (len > *size) {
+ *size = len;
+ return (UV_ENOBUFS);
+ }
+
+ *size = len;
+ memmove(buffer, buf, len);
+
+ return (0);
+}
+
+#define uv_os_setenv(name, value) setenv(name, value, 0)
+#endif /* UV_VERSION_HEX < UV_VERSION(1, 12, 0) */
+
+int
+isc_uv_udp_freebind(uv_udp_t *handle, const struct sockaddr *addr,
+ unsigned int flags);
+
+int
+isc_uv_tcp_freebind(uv_tcp_t *handle, const struct sockaddr *addr,
+ unsigned int flags);
diff --git a/lib/isc/netmgr/uverr2result.c b/lib/isc/netmgr/uverr2result.c
new file mode 100644
index 0000000..9f16ea8
--- /dev/null
+++ b/lib/isc/netmgr/uverr2result.c
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <uv.h>
+
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include "netmgr-int.h"
+
+/*%
+ * Convert a libuv error value into an isc_result_t. The
+ * list of supported error values is not complete; new users
+ * of this function should add any expected errors that are
+ * not already there.
+ */
+isc_result_t
+isc___nm_uverr2result(int uverr, bool dolog, const char *file,
+ unsigned int line, const char *func) {
+ switch (uverr) {
+ case 0:
+ return (ISC_R_SUCCESS);
+ case UV_ENOTDIR:
+ case UV_ELOOP:
+ case UV_EINVAL: /* XXX sometimes this is not for files */
+ case UV_ENAMETOOLONG:
+ case UV_EBADF:
+ return (ISC_R_INVALIDFILE);
+ case UV_ENOENT:
+ return (ISC_R_FILENOTFOUND);
+ case UV_EAGAIN:
+ return (ISC_R_NOCONN);
+ case UV_EACCES:
+ case UV_EPERM:
+ return (ISC_R_NOPERM);
+ case UV_EEXIST:
+ return (ISC_R_FILEEXISTS);
+ case UV_EIO:
+ return (ISC_R_IOERROR);
+ case UV_ENOMEM:
+ return (ISC_R_NOMEMORY);
+ case UV_ENFILE:
+ case UV_EMFILE:
+ return (ISC_R_TOOMANYOPENFILES);
+ case UV_ENOSPC:
+ return (ISC_R_DISCFULL);
+ case UV_EPIPE:
+ case UV_ECONNRESET:
+ case UV_ECONNABORTED:
+ return (ISC_R_CONNECTIONRESET);
+ case UV_ENOTCONN:
+ return (ISC_R_NOTCONNECTED);
+ case UV_ETIMEDOUT:
+ return (ISC_R_TIMEDOUT);
+ case UV_ENOBUFS:
+ return (ISC_R_NORESOURCES);
+ case UV_EAFNOSUPPORT:
+ return (ISC_R_FAMILYNOSUPPORT);
+ case UV_ENETDOWN:
+ return (ISC_R_NETDOWN);
+ case UV_EHOSTDOWN:
+ return (ISC_R_HOSTDOWN);
+ case UV_ENETUNREACH:
+ return (ISC_R_NETUNREACH);
+ case UV_EHOSTUNREACH:
+ return (ISC_R_HOSTUNREACH);
+ case UV_EADDRINUSE:
+ return (ISC_R_ADDRINUSE);
+ case UV_EADDRNOTAVAIL:
+ return (ISC_R_ADDRNOTAVAIL);
+ case UV_ECONNREFUSED:
+ return (ISC_R_CONNREFUSED);
+ case UV_ECANCELED:
+ return (ISC_R_CANCELED);
+ case UV_EOF:
+ return (ISC_R_EOF);
+ case UV_EMSGSIZE:
+ return (ISC_R_MAXSIZE);
+ case UV_ENOTSUP:
+ return (ISC_R_FAMILYNOSUPPORT);
+ case UV_ENOPROTOOPT:
+ case UV_EPROTONOSUPPORT:
+ return (ISC_R_INVALIDPROTO);
+ default:
+ if (dolog) {
+ UNEXPECTED_ERROR("unable to convert libuv error code "
+ "in %s (%s:%d) to isc_result: %d: %s",
+ func, file, line, uverr,
+ uv_strerror(uverr));
+ }
+ return (ISC_R_UNEXPECTED);
+ }
+}
diff --git a/lib/isc/netmgr_p.h b/lib/isc/netmgr_p.h
new file mode 100644
index 0000000..73171a9
--- /dev/null
+++ b/lib/isc/netmgr_p.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+#include <isc/mem.h>
+#include <isc/result.h>
+
+void
+isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netgmrp);
+/*%<
+ * Creates a new network manager with 'workers' worker threads,
+ * and starts it running.
+ */
+
+void
+isc__netmgr_destroy(isc_nm_t **netmgrp);
+/*%<
+ * Similar to isc_nm_detach(), but actively waits for all other references
+ * to be gone before returning.
+ */
+
+void
+isc__netmgr_shutdown(isc_nm_t *mgr);
+/*%<
+ * Shut down all active connections, freeing associated resources;
+ * prevent new connections from being established.
+ */
diff --git a/lib/isc/netscope.c b/lib/isc/netscope.c
new file mode 100644
index 0000000..69c3e9a
--- /dev/null
+++ b/lib/isc/netscope.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 <stdlib.h>
+
+#include <isc/net.h>
+#include <isc/netscope.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+isc_result_t
+isc_netscope_pton(int af, char *scopename, void *addr, uint32_t *zoneid) {
+ char *ep;
+#ifdef HAVE_IF_NAMETOINDEX
+ unsigned int ifid;
+ struct in6_addr *in6;
+#endif /* ifdef HAVE_IF_NAMETOINDEX */
+ uint32_t zone = 0;
+ uint64_t llz;
+
+#ifndef HAVE_IF_NAMETOINDEX
+ UNUSED(addr);
+#endif
+
+ /* at this moment, we only support AF_INET6 */
+ if (af != AF_INET6) {
+ return (ISC_R_FAILURE);
+ }
+
+ /*
+ * Basically, "names" are more stable than numeric IDs in terms
+ * of renumbering, and are more preferred. However, since there
+ * is no standard naming convention and APIs to deal with the
+ * names. Thus, we only handle the case of link-local
+ * addresses, for which we use interface names as link names,
+ * assuming one to one mapping between interfaces and links.
+ */
+#ifdef HAVE_IF_NAMETOINDEX
+ in6 = (struct in6_addr *)addr;
+ if (IN6_IS_ADDR_LINKLOCAL(in6) &&
+ (ifid = if_nametoindex((const char *)scopename)) != 0)
+ {
+ zone = (uint32_t)ifid;
+ } else {
+#endif /* ifdef HAVE_IF_NAMETOINDEX */
+ llz = strtoull(scopename, &ep, 10);
+ if (ep == scopename) {
+ return (ISC_R_FAILURE);
+ }
+
+ /* check overflow */
+ zone = (uint32_t)(llz & 0xffffffffUL);
+ if (zone != llz) {
+ return (ISC_R_FAILURE);
+ }
+#ifdef HAVE_IF_NAMETOINDEX
+ }
+#endif /* ifdef HAVE_IF_NAMETOINDEX */
+
+ *zoneid = zone;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/nonce.c b/lib/isc/nonce.c
new file mode 100644
index 0000000..4c2baff
--- /dev/null
+++ b/lib/isc/nonce.c
@@ -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.
+ */
+
+#include <isc/nonce.h>
+
+#include "entropy_private.h"
+
+void
+isc_nonce_buf(void *buf, size_t buflen) {
+ isc_entropy_get(buf, buflen);
+}
diff --git a/lib/isc/openssl_shim.c b/lib/isc/openssl_shim.c
new file mode 100644
index 0000000..b8dbfaa
--- /dev/null
+++ b/lib/isc/openssl_shim.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+#include <string.h>
+
+#include <openssl/crypto.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+
+#include "openssl_shim.h"
+
+#if !HAVE_CRYPTO_ZALLOC
+void *
+CRYPTO_zalloc(size_t num, const char *file, int line) {
+ void *ret = CRYPTO_malloc(num, file, line);
+ if (ret != NULL) {
+ memset(ret, 0, num);
+ }
+ return (ret);
+}
+#endif /* if !HAVE_CRYPTO_ZALLOC */
+
+#if !HAVE_EVP_CIPHER_CTX_NEW
+EVP_CIPHER_CTX *
+EVP_CIPHER_CTX_new(void) {
+ EVP_CIPHER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx));
+ return (ctx);
+}
+#endif /* if !HAVE_EVP_CIPHER_CTX_NEW */
+
+#if !HAVE_EVP_CIPHER_CTX_FREE
+void
+EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx) {
+ if (ctx != NULL) {
+ EVP_CIPHER_CTX_cleanup(ctx);
+ OPENSSL_free(ctx);
+ }
+}
+#endif /* if !HAVE_EVP_CIPHER_CTX_FREE */
+
+#if !HAVE_EVP_MD_CTX_RESET
+int
+EVP_MD_CTX_reset(EVP_MD_CTX *ctx) {
+ return (EVP_MD_CTX_cleanup(ctx));
+}
+#endif /* if !HAVE_EVP_MD_CTX_RESET */
+
+#if !HAVE_SSL_READ_EX
+int
+SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes) {
+ int rv = SSL_read(ssl, buf, num);
+ if (rv > 0) {
+ *readbytes = rv;
+ rv = 1;
+ }
+
+ return (rv);
+}
+#endif
+
+#if !HAVE_SSL_PEEK_EX
+int
+SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes) {
+ int rv = SSL_peek(ssl, buf, num);
+ if (rv > 0) {
+ *readbytes = rv;
+ rv = 1;
+ }
+
+ return (rv);
+}
+#endif
+
+#if !HAVE_SSL_WRITE_EX
+int
+SSL_write_ex(SSL *ssl, const void *buf, size_t num, size_t *written) {
+ int rv = SSL_write(ssl, buf, num);
+ if (rv > 0) {
+ *written = rv;
+ rv = 1;
+ }
+
+ return (rv);
+}
+#endif
+
+#if !HAVE_BIO_READ_EX
+int
+BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes) {
+ int rv = BIO_read(b, data, dlen);
+ if (rv > 0) {
+ *readbytes = rv;
+ rv = 1;
+ }
+
+ return (rv);
+}
+#endif
+
+#if !HAVE_BIO_WRITE_EX
+int
+BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written) {
+ int rv = BIO_write(b, data, dlen);
+ if (rv > 0) {
+ *written = rv;
+ rv = 1;
+ }
+
+ return (rv);
+}
+#endif
+
+#if !HAVE_OPENSSL_INIT_CRYPTO
+int
+OPENSSL_init_crypto(uint64_t opts, const void *settings) {
+ (void)settings;
+
+ if ((opts & OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS) == 0) {
+ ERR_load_crypto_strings();
+ }
+
+ if ((opts & (OPENSSL_INIT_NO_ADD_ALL_CIPHERS |
+ OPENSSL_INIT_NO_ADD_ALL_CIPHERS)) == 0)
+ {
+ OpenSSL_add_all_algorithms();
+ } else if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS) == 0) {
+ OpenSSL_add_all_digests();
+ } else if ((opts & OPENSSL_INIT_NO_ADD_ALL_CIPHERS) == 0) {
+ OpenSSL_add_all_ciphers();
+ }
+
+ return (1);
+}
+#endif
+
+#if !HAVE_OPENSSL_INIT_SSL
+int
+OPENSSL_init_ssl(uint64_t opts, const void *settings) {
+ OPENSSL_init_crypto(opts, settings);
+
+ SSL_library_init();
+
+ if ((opts & OPENSSL_INIT_NO_LOAD_SSL_STRINGS) == 0) {
+ SSL_load_error_strings();
+ }
+
+ return (1);
+}
+#endif
+
+#if !HAVE_OPENSSL_CLEANUP
+void
+OPENSSL_cleanup(void) {
+ return;
+}
+#endif
+
+#if !HAVE_SSL_CTX_UP_REF
+int
+SSL_CTX_up_ref(SSL_CTX *ctx) {
+ return (CRYPTO_add(&ctx->references, 1, CRYPTO_LOCK_SSL_CTX) > 0);
+}
+#endif /* !HAVE_SSL_CTX_UP_REF */
+
+#if !HAVE_X509_STORE_UP_REF
+
+int
+X509_STORE_up_ref(X509_STORE *store) {
+ return (CRYPTO_add(&store->references, 1, CRYPTO_LOCK_X509_STORE) > 0);
+}
+
+#endif /* !HAVE_OPENSSL_CLEANUP */
+
+#if !HAVE_SSL_CTX_SET1_CERT_STORE
+
+void
+SSL_CTX_set1_cert_store(SSL_CTX *ctx, X509_STORE *store) {
+ (void)X509_STORE_up_ref(store);
+
+ SSL_CTX_set_cert_store(ctx, store);
+}
+
+#endif /* !HAVE_SSL_CTX_SET1_CERT_STORE */
diff --git a/lib/isc/openssl_shim.h b/lib/isc/openssl_shim.h
new file mode 100644
index 0000000..c0abd14
--- /dev/null
+++ b/lib/isc/openssl_shim.h
@@ -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.
+ */
+
+#pragma once
+
+#include <openssl/crypto.h>
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/opensslv.h>
+#include <openssl/ssl.h>
+
+#if !HAVE_CRYPTO_ZALLOC
+void *
+CRYPTO_zalloc(size_t num, const char *file, int line);
+#endif /* if !HAVE_CRYPTO_ZALLOC */
+
+#if !defined(OPENSSL_zalloc)
+#define OPENSSL_zalloc(num) CRYPTO_zalloc(num, __FILE__, __LINE__)
+#endif
+
+#if !HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
+#define EVP_PKEY_new_raw_private_key(type, e, key, keylen) \
+ EVP_PKEY_new_mac_key(type, e, key, (int)(keylen))
+#endif /* if !HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY */
+
+#if !HAVE_EVP_CIPHER_CTX_NEW
+EVP_CIPHER_CTX *
+EVP_CIPHER_CTX_new(void);
+#endif /* if !HAVE_EVP_CIPHER_CTX_NEW */
+
+#if !HAVE_EVP_CIPHER_CTX_FREE
+void
+EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *ctx);
+#endif /* if !HAVE_EVP_CIPHER_CTX_FREE */
+
+#if !HAVE_EVP_MD_CTX_NEW
+#define EVP_MD_CTX_new EVP_MD_CTX_create
+#endif /* if !HAVE_EVP_MD_CTX_NEW */
+
+#if !HAVE_EVP_MD_CTX_FREE
+#define EVP_MD_CTX_free EVP_MD_CTX_destroy
+#endif /* if !HAVE_EVP_MD_CTX_FREE */
+
+#if !HAVE_EVP_MD_CTX_RESET
+int
+EVP_MD_CTX_reset(EVP_MD_CTX *ctx);
+#endif /* if !HAVE_EVP_MD_CTX_RESET */
+
+#if !HAVE_EVP_MD_CTX_GET0_MD
+#define EVP_MD_CTX_get0_md EVP_MD_CTX_md
+#endif /* if !HAVE_EVP_MD_CTX_GET0_MD */
+
+#if !HAVE_SSL_READ_EX
+int
+SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes);
+#endif
+
+#if !HAVE_SSL_PEEK_EX
+int
+SSL_peek_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes);
+#endif
+
+#if !HAVE_SSL_WRITE_EX
+int
+SSL_write_ex(SSL *ssl, const void *buf, size_t num, size_t *written);
+#endif
+
+#if !HAVE_BIO_READ_EX
+int
+BIO_read_ex(BIO *b, void *data, size_t dlen, size_t *readbytes);
+#endif
+
+#if !HAVE_BIO_WRITE_EX
+int
+BIO_write_ex(BIO *b, const void *data, size_t dlen, size_t *written);
+#endif
+
+#if !HAVE_OPENSSL_INIT_CRYPTO
+
+#define OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS 0x00000001L
+#define OPENSSL_INIT_LOAD_CRYPTO_STRINGS 0x00000002L
+#define OPENSSL_INIT_ADD_ALL_CIPHERS 0x00000004L
+#define OPENSSL_INIT_ADD_ALL_DIGESTS 0x00000008L
+#define OPENSSL_INIT_NO_ADD_ALL_CIPHERS 0x00000010L
+#define OPENSSL_INIT_NO_ADD_ALL_DIGESTS 0x00000020L
+
+int
+OPENSSL_init_crypto(uint64_t opts, const void *settings);
+#endif
+
+#if !HAVE_OPENSSL_INIT_SSL
+#define OPENSSL_INIT_NO_LOAD_SSL_STRINGS 0x00100000L
+#define OPENSSL_INIT_LOAD_SSL_STRINGS 0x00200000L
+
+int
+OPENSSL_init_ssl(uint64_t opts, const void *settings);
+
+#endif
+
+#if !HAVE_OPENSSL_CLEANUP
+void
+OPENSSL_cleanup(void);
+#endif
+
+#if !HAVE_TLS_SERVER_METHOD
+#define TLS_server_method SSLv23_server_method
+#endif
+
+#if !HAVE_TLS_CLIENT_METHOD
+#define TLS_client_method SSLv23_client_method
+#endif
+
+#if !HAVE_SSL_CTX_UP_REF
+int
+SSL_CTX_up_ref(SSL_CTX *store);
+#endif /* !HAVE_SSL_CTX_UP_REF */
+
+#if !HAVE_X509_STORE_UP_REF
+int
+X509_STORE_up_ref(X509_STORE *v);
+#endif /* !HAVE_OPENSSL_CLEANUP */
+
+#if !HAVE_SSL_CTX_SET1_CERT_STORE
+void
+SSL_CTX_set1_cert_store(SSL_CTX *ctx, X509_STORE *store);
+#endif /* !HAVE_SSL_CTX_SET1_CERT_STORE */
diff --git a/lib/isc/os.c b/lib/isc/os.c
new file mode 100644
index 0000000..0ba0fab
--- /dev/null
+++ b/lib/isc/os.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.
+ */
+
+#include <inttypes.h>
+#include <sys/stat.h>
+
+#include <isc/os.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include "os_p.h"
+
+static unsigned int isc__os_ncpus = 0;
+static unsigned long isc__os_cacheline = ISC_OS_CACHELINE_SIZE;
+static mode_t isc__os_umask = 0;
+
+#ifdef HAVE_SYSCONF
+
+#include <unistd.h>
+
+static long
+sysconf_ncpus(void) {
+#if defined(_SC_NPROCESSORS_ONLN)
+ return (sysconf((_SC_NPROCESSORS_ONLN)));
+#elif defined(_SC_NPROC_ONLN)
+ return (sysconf((_SC_NPROC_ONLN)));
+#else /* if defined(_SC_NPROCESSORS_ONLN) */
+ return (0);
+#endif /* if defined(_SC_NPROCESSORS_ONLN) */
+}
+#endif /* HAVE_SYSCONF */
+
+#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME)
+#include <sys/param.h> /* for NetBSD */
+#include <sys/sysctl.h>
+#include <sys/types.h> /* for FreeBSD */
+
+static int
+sysctl_ncpus(void) {
+ int ncpu, result;
+ size_t len;
+
+ len = sizeof(ncpu);
+ result = sysctlbyname("hw.ncpu", &ncpu, &len, 0, 0);
+ if (result != -1) {
+ return (ncpu);
+ }
+ return (0);
+}
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */
+
+static void
+ncpus_initialize(void) {
+#if defined(HAVE_SYSCONF)
+ isc__os_ncpus = sysconf_ncpus();
+#endif /* if defined(HAVE_SYSCONF) */
+#if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME)
+ if (isc__os_ncpus <= 0) {
+ isc__os_ncpus = sysctl_ncpus();
+ }
+#endif /* if defined(HAVE_SYS_SYSCTL_H) && defined(HAVE_SYSCTLBYNAME) */
+ if (isc__os_ncpus == 0) {
+ isc__os_ncpus = 1;
+ }
+}
+
+static void
+umask_initialize(void) {
+ isc__os_umask = umask(0);
+ (void)umask(isc__os_umask);
+}
+
+unsigned int
+isc_os_ncpus(void) {
+ return (isc__os_ncpus);
+}
+
+unsigned long
+isc_os_cacheline(void) {
+ return (isc__os_cacheline);
+}
+
+mode_t
+isc_os_umask(void) {
+ return (isc__os_umask);
+}
+
+void
+isc__os_initialize(void) {
+ umask_initialize();
+ ncpus_initialize();
+#if defined(HAVE_SYSCONF) && defined(_SC_LEVEL1_DCACHE_LINESIZE)
+ long s = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
+ if (s > 0 && (unsigned long)s > isc__os_cacheline) {
+ isc__os_cacheline = s;
+ }
+#endif
+}
+
+void
+isc__os_shutdown(void) {
+ /* empty, but defined for completeness */;
+}
diff --git a/lib/isc/os_p.h b/lib/isc/os_p.h
new file mode 100644
index 0000000..c39bc23
--- /dev/null
+++ b/lib/isc/os_p.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
+
+#include <stdio.h>
+
+#include <isc/os.h>
+
+/*! \file */
+
+void
+isc__os_initialize(void);
+
+void
+isc__os_shutdown(void);
diff --git a/lib/isc/parseint.c b/lib/isc/parseint.c
new file mode 100644
index 0000000..da6c281
--- /dev/null
+++ b/lib/isc/parseint.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdlib.h>
+
+#include <isc/parseint.h>
+#include <isc/result.h>
+
+isc_result_t
+isc_parse_uint32(uint32_t *uip, const char *string, int base) {
+ unsigned long n;
+ uint32_t r;
+ char *e;
+ if (!isalnum((unsigned char)(string[0]))) {
+ return (ISC_R_BADNUMBER);
+ }
+ errno = 0;
+ n = strtoul(string, &e, base);
+ if (*e != '\0') {
+ return (ISC_R_BADNUMBER);
+ }
+ /*
+ * Where long is 64 bits we need to convert to 32 bits then test for
+ * equality. This is a no-op on 32 bit machines and a good compiler
+ * will optimise it away.
+ */
+ r = (uint32_t)n;
+ if ((n == ULONG_MAX && errno == ERANGE) || (n != (unsigned long)r)) {
+ return (ISC_R_RANGE);
+ }
+ *uip = r;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_parse_uint16(uint16_t *uip, const char *string, int base) {
+ uint32_t val;
+ isc_result_t result;
+ result = isc_parse_uint32(&val, string, base);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (val > 0xFFFF) {
+ return (ISC_R_RANGE);
+ }
+ *uip = (uint16_t)val;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_parse_uint8(uint8_t *uip, const char *string, int base) {
+ uint32_t val;
+ isc_result_t result;
+ result = isc_parse_uint32(&val, string, base);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (val > 0xFF) {
+ return (ISC_R_RANGE);
+ }
+ *uip = (uint8_t)val;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/picohttpparser.c b/lib/isc/picohttpparser.c
new file mode 100644
index 0000000..d82fd01
--- /dev/null
+++ b/lib/isc/picohttpparser.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
+ * Shigeo Mitsunari
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * The software is licensed under either the MIT License (below) or the Perl
+ * license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef __SSE4_2__
+#ifdef _MSC_VER
+#include <nmmintrin.h>
+#else
+#include <x86intrin.h>
+#endif
+#endif
+#include "picohttpparser.h"
+
+#if __GNUC__ >= 3
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#else
+#define likely(x) (x)
+#define unlikely(x) (x)
+#endif
+
+#ifdef _MSC_VER
+#define ALIGNED(n) _declspec(align(n))
+#else
+#define ALIGNED(n) __attribute__((aligned(n)))
+#endif
+
+#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u)
+
+#define CHECK_EOF() \
+ if (buf == buf_end) { \
+ *ret = -2; \
+ return NULL; \
+ }
+
+#define EXPECT_CHAR_NO_CHECK(ch) \
+ if (*buf++ != ch) { \
+ *ret = -1; \
+ return NULL; \
+ }
+
+#define EXPECT_CHAR(ch) \
+ CHECK_EOF(); \
+ EXPECT_CHAR_NO_CHECK(ch);
+
+#define ADVANCE_TOKEN(tok, toklen) \
+ do { \
+ const char *tok_start = buf; \
+ static const char ALIGNED(16) \
+ ranges2[16] = "\000\040\177\177"; \
+ int found2; \
+ buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \
+ if (!found2) { \
+ CHECK_EOF(); \
+ } \
+ while (1) { \
+ if (*buf == ' ') { \
+ break; \
+ } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \
+ if ((unsigned char)*buf < '\040' || \
+ *buf == '\177') \
+ { \
+ *ret = -1; \
+ return NULL; \
+ } \
+ } \
+ ++buf; \
+ CHECK_EOF(); \
+ } \
+ tok = tok_start; \
+ toklen = buf - tok_start; \
+ } while (0)
+
+static const char *token_char_map =
+ "\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\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0"
+ "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1"
+ "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\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\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\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\0\0\0\0\0";
+
+static const char *
+findchar_fast(const char *buf, const char *buf_end, const char *ranges,
+ size_t ranges_size, int *found) {
+ *found = 0;
+#if __SSE4_2__
+ if (likely(buf_end - buf >= 16)) {
+ __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges);
+
+ size_t left = (buf_end - buf) & ~15;
+ do {
+ __m128i b16 = _mm_loadu_si128((const __m128i *)buf);
+ int r = _mm_cmpestri(ranges16, ranges_size, b16, 16,
+ _SIDD_LEAST_SIGNIFICANT |
+ _SIDD_CMP_RANGES |
+ _SIDD_UBYTE_OPS);
+ if (unlikely(r != 16)) {
+ buf += r;
+ *found = 1;
+ break;
+ }
+ buf += 16;
+ left -= 16;
+ } while (likely(left != 0));
+ }
+#else
+ /* suppress unused parameter warning */
+ (void)buf_end;
+ (void)ranges;
+ (void)ranges_size;
+#endif
+ return buf;
+}
+
+static const char *
+get_token_to_eol(const char *buf, const char *buf_end, const char **token,
+ size_t *token_len, int *ret) {
+ const char *token_start = buf;
+
+#ifdef __SSE4_2__
+ static const char ALIGNED(16)
+ ranges1[16] = "\0\010" /* allow HT */
+ "\012\037" /* allow SP and up to but not including
+ DEL */
+ "\177\177"; /* allow chars w. MSB set */
+ int found;
+ buf = findchar_fast(buf, buf_end, ranges1, 6, &found);
+ if (found)
+ goto FOUND_CTL;
+#else
+ /* find non-printable char within the next 8 bytes, this is the hottest
+ * code; manually inlined */
+ while (likely(buf_end - buf >= 8)) {
+#define DOIT() \
+ do { \
+ if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \
+ goto NonPrintable; \
+ ++buf; \
+ } while (0)
+ DOIT();
+ DOIT();
+ DOIT();
+ DOIT();
+ DOIT();
+ DOIT();
+ DOIT();
+ DOIT();
+#undef DOIT
+ continue;
+ NonPrintable:
+ if ((likely((unsigned char)*buf < '\040') &&
+ likely(*buf != '\011')) ||
+ unlikely(*buf == '\177'))
+ {
+ goto FOUND_CTL;
+ }
+ ++buf;
+ }
+#endif
+ for (;; ++buf) {
+ CHECK_EOF();
+ if (unlikely(!IS_PRINTABLE_ASCII(*buf))) {
+ if ((likely((unsigned char)*buf < '\040') &&
+ likely(*buf != '\011')) ||
+ unlikely(*buf == '\177'))
+ {
+ goto FOUND_CTL;
+ }
+ }
+ }
+FOUND_CTL:
+ if (likely(*buf == '\015')) {
+ ++buf;
+ EXPECT_CHAR('\012');
+ *token_len = buf - 2 - token_start;
+ } else if (*buf == '\012') {
+ *token_len = buf - token_start;
+ ++buf;
+ } else {
+ *ret = -1;
+ return NULL;
+ }
+ *token = token_start;
+
+ return buf;
+}
+
+static const char *
+is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) {
+ int ret_cnt = 0;
+ buf = last_len < 3 ? buf : buf + last_len - 3;
+
+ while (1) {
+ CHECK_EOF();
+ if (*buf == '\015') {
+ ++buf;
+ CHECK_EOF();
+ EXPECT_CHAR('\012');
+ ++ret_cnt;
+ } else if (*buf == '\012') {
+ ++buf;
+ ++ret_cnt;
+ } else {
+ ++buf;
+ ret_cnt = 0;
+ }
+ if (ret_cnt == 2) {
+ return buf;
+ }
+ }
+
+ *ret = -2;
+ return NULL;
+}
+
+#define PARSE_INT(valp_, mul_) \
+ if (*buf < '0' || '9' < *buf) { \
+ buf++; \
+ *ret = -1; \
+ return NULL; \
+ } \
+ *(valp_) = (mul_) * (*buf++ - '0');
+
+#define PARSE_INT_3(valp_) \
+ do { \
+ int res_ = 0; \
+ PARSE_INT(&res_, 100) \
+ *valp_ = res_; \
+ PARSE_INT(&res_, 10) \
+ *valp_ += res_; \
+ PARSE_INT(&res_, 1) \
+ *valp_ += res_; \
+ } while (0)
+
+/* returned pointer is always within [buf, buf_end), or null */
+static const char *
+parse_token(const char *buf, const char *buf_end, const char **token,
+ size_t *token_len, char next_char, int *ret) {
+ /* We use pcmpestri to detect non-token characters. This instruction can
+ * take no more than eight character ranges (8*2*8=128 bits that is the
+ * size of a SSE register). Due to this restriction, characters `|` and
+ * `~` are handled in the slow loop. */
+ static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up
+ to SP */
+ "\"\"" /* 0x22 */
+ "()" /* 0x28,0x29 */
+ ",," /* 0x2c */
+ "//" /* 0x2f */
+ ":@" /* 0x3a-0x40 */
+ "[]" /* 0x5b-0x5d */
+ "{\xff"; /* 0x7b-0xff */
+ const char *buf_start = buf;
+ int found;
+ buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found);
+ if (!found) {
+ CHECK_EOF();
+ }
+ while (1) {
+ if (*buf == next_char) {
+ break;
+ } else if (!token_char_map[(unsigned char)*buf]) {
+ *ret = -1;
+ return NULL;
+ }
+ ++buf;
+ CHECK_EOF();
+ }
+ *token = buf_start;
+ *token_len = buf - buf_start;
+ return buf;
+}
+
+/* returned pointer is always within [buf, buf_end), or null */
+static const char *
+parse_http_version(const char *buf, const char *buf_end, int *minor_version,
+ int *ret) {
+ /* we want at least [HTTP/1.<two chars>] to try to parse */
+ if (buf_end - buf < 9) {
+ *ret = -2;
+ return NULL;
+ }
+ EXPECT_CHAR_NO_CHECK('H');
+ EXPECT_CHAR_NO_CHECK('T');
+ EXPECT_CHAR_NO_CHECK('T');
+ EXPECT_CHAR_NO_CHECK('P');
+ EXPECT_CHAR_NO_CHECK('/');
+ EXPECT_CHAR_NO_CHECK('1');
+ EXPECT_CHAR_NO_CHECK('.');
+ PARSE_INT(minor_version, 1);
+ return buf;
+}
+
+static const char *
+parse_headers(const char *buf, const char *buf_end, struct phr_header *headers,
+ size_t *num_headers, size_t max_headers, int *ret) {
+ for (;; ++*num_headers) {
+ CHECK_EOF();
+ if (*buf == '\015') {
+ ++buf;
+ EXPECT_CHAR('\012');
+ break;
+ } else if (*buf == '\012') {
+ ++buf;
+ break;
+ }
+ if (*num_headers == max_headers) {
+ *ret = -1;
+ return NULL;
+ }
+ if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) {
+ /* parsing name, but do not discard SP before colon, see
+ * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html
+ */
+ if ((buf = parse_token(buf, buf_end,
+ &headers[*num_headers].name,
+ &headers[*num_headers].name_len,
+ ':', ret)) == NULL)
+ {
+ return NULL;
+ }
+ if (headers[*num_headers].name_len == 0) {
+ *ret = -1;
+ return NULL;
+ }
+ ++buf;
+ for (;; ++buf) {
+ CHECK_EOF();
+ if (!(*buf == ' ' || *buf == '\t')) {
+ break;
+ }
+ }
+ } else {
+ headers[*num_headers].name = NULL;
+ headers[*num_headers].name_len = 0;
+ }
+ const char *value;
+ size_t value_len;
+ if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len,
+ ret)) == NULL)
+ {
+ return NULL;
+ }
+ /* remove trailing SPs and HTABs */
+ const char *value_end = value + value_len;
+ for (; value_end != value; --value_end) {
+ const char c = *(value_end - 1);
+ if (!(c == ' ' || c == '\t')) {
+ break;
+ }
+ }
+ headers[*num_headers].value = value;
+ headers[*num_headers].value_len = value_end - value;
+ }
+ return buf;
+}
+
+static const char *
+parse_request(const char *buf, const char *buf_end, const char **method,
+ size_t *method_len, const char **path, size_t *path_len,
+ int *minor_version, struct phr_header *headers,
+ size_t *num_headers, size_t max_headers, int *ret) {
+ /* skip first empty line (some clients add CRLF after POST content) */
+ CHECK_EOF();
+ if (*buf == '\015') {
+ ++buf;
+ EXPECT_CHAR('\012');
+ } else if (*buf == '\012') {
+ ++buf;
+ }
+
+ /* parse request line */
+ if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) ==
+ NULL)
+ {
+ return NULL;
+ }
+ do {
+ ++buf;
+ CHECK_EOF();
+ } while (*buf == ' ');
+ ADVANCE_TOKEN(*path, *path_len);
+ do {
+ ++buf;
+ CHECK_EOF();
+ } while (*buf == ' ');
+ if (*method_len == 0 || *path_len == 0) {
+ *ret = -1;
+ return NULL;
+ }
+ if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
+ NULL)
+ {
+ return NULL;
+ }
+ if (*buf == '\015') {
+ ++buf;
+ EXPECT_CHAR('\012');
+ } else if (*buf == '\012') {
+ ++buf;
+ } else {
+ *ret = -1;
+ return NULL;
+ }
+
+ return parse_headers(buf, buf_end, headers, num_headers, max_headers,
+ ret);
+}
+
+int
+phr_parse_request(const char *buf_start, size_t len, const char **method,
+ size_t *method_len, const char **path, size_t *path_len,
+ int *minor_version, struct phr_header *headers,
+ size_t *num_headers, size_t last_len) {
+ const char *buf = buf_start, *buf_end = buf_start + len;
+ size_t max_headers = *num_headers;
+ int r = -1;
+
+ *method = NULL;
+ *method_len = 0;
+ *path = NULL;
+ *path_len = 0;
+ *minor_version = -1;
+ *num_headers = 0;
+
+ /* if last_len != 0, check if the request is complete (a fast
+ countermeasure againt slowloris */
+ if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+ return r;
+ }
+
+ if ((buf = parse_request(buf, buf_end, method, method_len, path,
+ path_len, minor_version, headers, num_headers,
+ max_headers, &r)) == NULL)
+ {
+ return r;
+ }
+
+ return (int)(buf - buf_start);
+}
+
+static const char *
+parse_response(const char *buf, const char *buf_end, int *minor_version,
+ int *status, const char **msg, size_t *msg_len,
+ struct phr_header *headers, size_t *num_headers,
+ size_t max_headers, int *ret) {
+ /* parse "HTTP/1.x" */
+ if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) ==
+ NULL)
+ {
+ return NULL;
+ }
+ /* skip space */
+ if (*buf != ' ') {
+ *ret = -1;
+ return NULL;
+ }
+ do {
+ ++buf;
+ CHECK_EOF();
+ } while (*buf == ' ');
+ /* parse status code, we want at least [:digit:][:digit:][:digit:]<other
+ * char> to try to parse */
+ if (buf_end - buf < 4) {
+ *ret = -2;
+ return NULL;
+ }
+ PARSE_INT_3(status);
+
+ /* get message including preceding space */
+ if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) {
+ return NULL;
+ }
+ if (*msg_len == 0) {
+ /* ok */
+ } else if (**msg == ' ') {
+ /* Remove preceding space. Successful return from
+ * `get_token_to_eol` guarantees that we would hit something
+ * other than SP before running past the end of the given
+ * buffer. */
+ do {
+ ++*msg;
+ --*msg_len;
+ } while (**msg == ' ');
+ } else {
+ /* garbage found after status code */
+ *ret = -1;
+ return NULL;
+ }
+
+ return parse_headers(buf, buf_end, headers, num_headers, max_headers,
+ ret);
+}
+
+int
+phr_parse_response(const char *buf_start, size_t len, int *minor_version,
+ int *status, const char **msg, size_t *msg_len,
+ struct phr_header *headers, size_t *num_headers,
+ size_t last_len) {
+ const char *buf = buf_start, *buf_end = buf + len;
+ size_t max_headers = *num_headers;
+ int r;
+
+ *minor_version = -1;
+ *status = 0;
+ *msg = NULL;
+ *msg_len = 0;
+ *num_headers = 0;
+
+ /* if last_len != 0, check if the response is complete (a fast
+ countermeasure against slowloris */
+ if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+ return r;
+ }
+
+ if ((buf = parse_response(buf, buf_end, minor_version, status, msg,
+ msg_len, headers, num_headers, max_headers,
+ &r)) == NULL)
+ {
+ return r;
+ }
+
+ return (int)(buf - buf_start);
+}
+
+int
+phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers,
+ size_t *num_headers, size_t last_len) {
+ const char *buf = buf_start, *buf_end = buf + len;
+ size_t max_headers = *num_headers;
+ int r;
+
+ *num_headers = 0;
+
+ /* if last_len != 0, check if the response is complete (a fast
+ countermeasure against slowloris */
+ if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) {
+ return r;
+ }
+
+ if ((buf = parse_headers(buf, buf_end, headers, num_headers,
+ max_headers, &r)) == NULL)
+ {
+ return r;
+ }
+
+ return (int)(buf - buf_start);
+}
+
+enum {
+ CHUNKED_IN_CHUNK_SIZE,
+ CHUNKED_IN_CHUNK_EXT,
+ CHUNKED_IN_CHUNK_DATA,
+ CHUNKED_IN_CHUNK_CRLF,
+ CHUNKED_IN_TRAILERS_LINE_HEAD,
+ CHUNKED_IN_TRAILERS_LINE_MIDDLE
+};
+
+static int
+decode_hex(int ch) {
+ if ('0' <= ch && ch <= '9') {
+ return ch - '0';
+ } else if ('A' <= ch && ch <= 'F') {
+ return ch - 'A' + 0xa;
+ } else if ('a' <= ch && ch <= 'f') {
+ return ch - 'a' + 0xa;
+ } else {
+ return -1;
+ }
+}
+
+ssize_t
+phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf,
+ size_t *_bufsz) {
+ size_t dst = 0, src = 0, bufsz = *_bufsz;
+ ssize_t ret = -2; /* incomplete */
+
+ while (1) {
+ switch (decoder->_state) {
+ case CHUNKED_IN_CHUNK_SIZE:
+ for (;; ++src) {
+ int v;
+ if (src == bufsz)
+ goto Exit;
+ if ((v = decode_hex(buf[src])) == -1) {
+ if (decoder->_hex_count == 0) {
+ ret = -1;
+ goto Exit;
+ }
+ break;
+ }
+ if (decoder->_hex_count == sizeof(size_t) * 2) {
+ ret = -1;
+ goto Exit;
+ }
+ decoder->bytes_left_in_chunk =
+ decoder->bytes_left_in_chunk * 16 + v;
+ ++decoder->_hex_count;
+ }
+ decoder->_hex_count = 0;
+ decoder->_state = CHUNKED_IN_CHUNK_EXT;
+ /* fallthru */
+ case CHUNKED_IN_CHUNK_EXT:
+ /* RFC 7230 A.2 "Line folding in chunk extensions is
+ * disallowed" */
+ for (;; ++src) {
+ if (src == bufsz)
+ goto Exit;
+ if (buf[src] == '\012')
+ break;
+ }
+ ++src;
+ if (decoder->bytes_left_in_chunk == 0) {
+ if (decoder->consume_trailer) {
+ decoder->_state =
+ CHUNKED_IN_TRAILERS_LINE_HEAD;
+ break;
+ } else {
+ goto Complete;
+ }
+ }
+ decoder->_state = CHUNKED_IN_CHUNK_DATA;
+ /* fallthru */
+ case CHUNKED_IN_CHUNK_DATA: {
+ size_t avail = bufsz - src;
+ if (avail < decoder->bytes_left_in_chunk) {
+ if (dst != src)
+ memmove(buf + dst, buf + src, avail);
+ src += avail;
+ dst += avail;
+ decoder->bytes_left_in_chunk -= avail;
+ goto Exit;
+ }
+ if (dst != src)
+ memmove(buf + dst, buf + src,
+ decoder->bytes_left_in_chunk);
+ src += decoder->bytes_left_in_chunk;
+ dst += decoder->bytes_left_in_chunk;
+ decoder->bytes_left_in_chunk = 0;
+ decoder->_state = CHUNKED_IN_CHUNK_CRLF;
+ }
+ /* fallthru */
+ case CHUNKED_IN_CHUNK_CRLF:
+ for (;; ++src) {
+ if (src == bufsz)
+ goto Exit;
+ if (buf[src] != '\015')
+ break;
+ }
+ if (buf[src] != '\012') {
+ ret = -1;
+ goto Exit;
+ }
+ ++src;
+ decoder->_state = CHUNKED_IN_CHUNK_SIZE;
+ break;
+ case CHUNKED_IN_TRAILERS_LINE_HEAD:
+ for (;; ++src) {
+ if (src == bufsz)
+ goto Exit;
+ if (buf[src] != '\015')
+ break;
+ }
+ if (buf[src++] == '\012')
+ goto Complete;
+ decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE;
+ /* fallthru */
+ case CHUNKED_IN_TRAILERS_LINE_MIDDLE:
+ for (;; ++src) {
+ if (src == bufsz)
+ goto Exit;
+ if (buf[src] == '\012')
+ break;
+ }
+ ++src;
+ decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD;
+ break;
+ default:
+ assert(!"decoder is corrupt");
+ }
+ }
+
+Complete:
+ ret = bufsz - src;
+Exit:
+ if (dst != src)
+ memmove(buf + dst, buf + src, bufsz - src);
+ *_bufsz = dst;
+ return ret;
+}
+
+int
+phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) {
+ return decoder->_state == CHUNKED_IN_CHUNK_DATA;
+}
+
+#undef CHECK_EOF
+#undef EXPECT_CHAR
+#undef ADVANCE_TOKEN
diff --git a/lib/isc/picohttpparser.h b/lib/isc/picohttpparser.h
new file mode 100644
index 0000000..0657cb2
--- /dev/null
+++ b/lib/isc/picohttpparser.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase,
+ * Shigeo Mitsunari
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * The software is licensed under either the MIT License (below) or the Perl
+ * license.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef picohttpparser_h
+#define picohttpparser_h
+
+#include <sys/types.h>
+
+#ifdef _MSC_VER
+#define ssize_t intptr_t
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* contains name and value of a header (name == NULL if is a continuing line
+ * of a multiline header */
+struct phr_header {
+ const char *name;
+ size_t name_len;
+ const char *value;
+ size_t value_len;
+};
+
+/* returns number of bytes consumed if successful, -2 if request is partial,
+ * -1 if failed */
+int
+phr_parse_request(const char *buf, size_t len, const char **method,
+ size_t *method_len, const char **path, size_t *path_len,
+ int *minor_version, struct phr_header *headers,
+ size_t *num_headers, size_t last_len);
+
+/* ditto */
+int
+phr_parse_response(const char *_buf, size_t len, int *minor_version,
+ int *status, const char **msg, size_t *msg_len,
+ struct phr_header *headers, size_t *num_headers,
+ size_t last_len);
+
+/* ditto */
+int
+phr_parse_headers(const char *buf, size_t len, struct phr_header *headers,
+ size_t *num_headers, size_t last_len);
+
+/* should be zero-filled before start */
+struct phr_chunked_decoder {
+ size_t bytes_left_in_chunk; /* number of bytes left in current chunk */
+ char consume_trailer; /* if trailing headers should be consumed */
+ char _hex_count;
+ char _state;
+};
+
+/* the function rewrites the buffer given as (buf, bufsz) removing the chunked-
+ * encoding headers. When the function returns without an error, bufsz is
+ * updated to the length of the decoded data available. Applications should
+ * repeatedly call the function while it returns -2 (incomplete) every time
+ * supplying newly arrived data. If the end of the chunked-encoded data is
+ * found, the function returns a non-negative number indicating the number of
+ * octets left undecoded, that starts from the offset returned by `*bufsz`.
+ * Returns -1 on error.
+ */
+ssize_t
+phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf,
+ size_t *bufsz);
+
+/* returns if the chunked decoder is in middle of chunked data */
+int
+phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/isc/pool.c b/lib/isc/pool.c
new file mode 100644
index 0000000..5a5fb12
--- /dev/null
+++ b/lib/isc/pool.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.
+ */
+
+/*! \file */
+
+#include <string.h>
+
+#include <isc/mem.h>
+#include <isc/pool.h>
+#include <isc/random.h>
+#include <isc/util.h>
+
+/***
+ *** Types.
+ ***/
+
+struct isc_pool {
+ isc_mem_t *mctx;
+ unsigned int count;
+ isc_pooldeallocator_t free;
+ isc_poolinitializer_t init;
+ void *initarg;
+ void **pool;
+};
+
+/***
+ *** Functions.
+ ***/
+
+static isc_result_t
+alloc_pool(isc_mem_t *mctx, unsigned int count, isc_pool_t **poolp) {
+ isc_pool_t *pool;
+
+ pool = isc_mem_get(mctx, sizeof(*pool));
+ pool->count = count;
+ pool->free = NULL;
+ pool->init = NULL;
+ pool->initarg = NULL;
+ pool->mctx = NULL;
+ isc_mem_attach(mctx, &pool->mctx);
+ pool->pool = isc_mem_get(mctx, count * sizeof(void *));
+ memset(pool->pool, 0, count * sizeof(void *));
+
+ *poolp = pool;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_pool_create(isc_mem_t *mctx, unsigned int count,
+ isc_pooldeallocator_t release, isc_poolinitializer_t init,
+ void *initarg, isc_pool_t **poolp) {
+ isc_pool_t *pool = NULL;
+ isc_result_t result;
+ unsigned int i;
+
+ INSIST(count > 0);
+
+ /* Allocate the pool structure */
+ result = alloc_pool(mctx, count, &pool);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ pool->free = release;
+ pool->init = init;
+ pool->initarg = initarg;
+
+ /* Populate the pool */
+ for (i = 0; i < count; i++) {
+ result = init(&pool->pool[i], initarg);
+ if (result != ISC_R_SUCCESS) {
+ isc_pool_destroy(&pool);
+ return (result);
+ }
+ }
+
+ *poolp = pool;
+ return (ISC_R_SUCCESS);
+}
+
+void *
+isc_pool_get(isc_pool_t *pool) {
+ return (pool->pool[isc_random_uniform(pool->count)]);
+}
+
+int
+isc_pool_count(isc_pool_t *pool) {
+ REQUIRE(pool != NULL);
+ return (pool->count);
+}
+
+isc_result_t
+isc_pool_expand(isc_pool_t **sourcep, unsigned int count,
+ isc_pool_t **targetp) {
+ isc_result_t result;
+ isc_pool_t *pool;
+
+ REQUIRE(sourcep != NULL && *sourcep != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ pool = *sourcep;
+ *sourcep = NULL;
+ if (count > pool->count) {
+ isc_pool_t *newpool = NULL;
+ unsigned int i;
+
+ /* Allocate a new pool structure */
+ result = alloc_pool(pool->mctx, count, &newpool);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ newpool->free = pool->free;
+ newpool->init = pool->init;
+ newpool->initarg = pool->initarg;
+
+ /* Populate the new entries */
+ for (i = pool->count; i < count; i++) {
+ result = newpool->init(&newpool->pool[i],
+ newpool->initarg);
+ if (result != ISC_R_SUCCESS) {
+ isc_pool_destroy(&newpool);
+ return (result);
+ }
+ }
+
+ /* Copy over the objects from the old pool */
+ for (i = 0; i < pool->count; i++) {
+ newpool->pool[i] = pool->pool[i];
+ pool->pool[i] = NULL;
+ }
+
+ isc_pool_destroy(&pool);
+ pool = newpool;
+ }
+
+ *targetp = pool;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_pool_destroy(isc_pool_t **poolp) {
+ unsigned int i;
+ isc_pool_t *pool = *poolp;
+ *poolp = NULL;
+ for (i = 0; i < pool->count; i++) {
+ if (pool->free != NULL && pool->pool[i] != NULL) {
+ pool->free(&pool->pool[i]);
+ }
+ }
+ isc_mem_put(pool->mctx, pool->pool, pool->count * sizeof(void *));
+ isc_mem_putanddetach(&pool->mctx, pool, sizeof(*pool));
+}
diff --git a/lib/isc/portset.c b/lib/isc/portset.c
new file mode 100644
index 0000000..52b96f5
--- /dev/null
+++ b/lib/isc/portset.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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/portset.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#define ISC_PORTSET_BUFSIZE (65536 / (sizeof(uint32_t) * 8))
+
+/*%
+ * Internal representation of portset. It's an array of 32-bit integers, each
+ * bit corresponding to a single port in the ascending order. For example,
+ * the second most significant bit of buf[0] corresponds to port 1.
+ */
+struct isc_portset {
+ unsigned int nports; /*%< number of ports in the set */
+ uint32_t buf[ISC_PORTSET_BUFSIZE];
+};
+
+static bool
+portset_isset(isc_portset_t *portset, in_port_t port) {
+ return ((portset->buf[port >> 5] & ((uint32_t)1 << (port & 31))) != 0);
+}
+
+static void
+portset_add(isc_portset_t *portset, in_port_t port) {
+ if (!portset_isset(portset, port)) {
+ portset->nports++;
+ portset->buf[port >> 5] |= ((uint32_t)1 << (port & 31));
+ }
+}
+
+static void
+portset_remove(isc_portset_t *portset, in_port_t port) {
+ if (portset_isset(portset, port)) {
+ portset->nports--;
+ portset->buf[port >> 5] &= ~((uint32_t)1 << (port & 31));
+ }
+}
+
+isc_result_t
+isc_portset_create(isc_mem_t *mctx, isc_portset_t **portsetp) {
+ isc_portset_t *portset;
+
+ REQUIRE(portsetp != NULL && *portsetp == NULL);
+
+ portset = isc_mem_get(mctx, sizeof(*portset));
+
+ /* Make the set 'empty' by default */
+ memset(portset, 0, sizeof(*portset));
+ *portsetp = portset;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_portset_destroy(isc_mem_t *mctx, isc_portset_t **portsetp) {
+ isc_portset_t *portset;
+
+ REQUIRE(portsetp != NULL);
+ portset = *portsetp;
+
+ isc_mem_put(mctx, portset, sizeof(*portset));
+}
+
+bool
+isc_portset_isset(isc_portset_t *portset, in_port_t port) {
+ REQUIRE(portset != NULL);
+
+ return (portset_isset(portset, port));
+}
+
+unsigned int
+isc_portset_nports(isc_portset_t *portset) {
+ REQUIRE(portset != NULL);
+
+ return (portset->nports);
+}
+
+void
+isc_portset_add(isc_portset_t *portset, in_port_t port) {
+ REQUIRE(portset != NULL);
+
+ portset_add(portset, port);
+}
+
+void
+isc_portset_remove(isc_portset_t *portset, in_port_t port) {
+ portset_remove(portset, port);
+}
+
+void
+isc_portset_addrange(isc_portset_t *portset, in_port_t port_lo,
+ in_port_t port_hi) {
+ in_port_t p;
+
+ REQUIRE(portset != NULL);
+ REQUIRE(port_lo <= port_hi);
+
+ p = port_lo;
+ do {
+ portset_add(portset, p);
+ } while (p++ < port_hi);
+}
+
+void
+isc_portset_removerange(isc_portset_t *portset, in_port_t port_lo,
+ in_port_t port_hi) {
+ in_port_t p;
+
+ REQUIRE(portset != NULL);
+ REQUIRE(port_lo <= port_hi);
+
+ p = port_lo;
+ do {
+ portset_remove(portset, p);
+ } while (p++ < port_hi);
+}
diff --git a/lib/isc/quota.c b/lib/isc/quota.c
new file mode 100644
index 0000000..5465db5
--- /dev/null
+++ b/lib/isc/quota.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/atomic.h>
+#include <isc/quota.h>
+#include <isc/util.h>
+
+#define QUOTA_MAGIC ISC_MAGIC('Q', 'U', 'O', 'T')
+#define VALID_QUOTA(p) ISC_MAGIC_VALID(p, QUOTA_MAGIC)
+
+#define QUOTA_CB_MAGIC ISC_MAGIC('Q', 'T', 'C', 'B')
+#define VALID_QUOTA_CB(p) ISC_MAGIC_VALID(p, QUOTA_CB_MAGIC)
+
+void
+isc_quota_init(isc_quota_t *quota, unsigned int max) {
+ atomic_init(&quota->max, max);
+ atomic_init(&quota->used, 0);
+ atomic_init(&quota->soft, 0);
+ atomic_init(&quota->waiting, 0);
+ ISC_LIST_INIT(quota->cbs);
+ isc_mutex_init(&quota->cblock);
+ ISC_LINK_INIT(quota, link);
+ quota->magic = QUOTA_MAGIC;
+}
+
+void
+isc_quota_destroy(isc_quota_t *quota) {
+ REQUIRE(VALID_QUOTA(quota));
+ quota->magic = 0;
+
+ INSIST(atomic_load(&quota->used) == 0);
+ INSIST(atomic_load(&quota->waiting) == 0);
+ INSIST(ISC_LIST_EMPTY(quota->cbs));
+ atomic_store_release(&quota->max, 0);
+ atomic_store_release(&quota->used, 0);
+ atomic_store_release(&quota->soft, 0);
+ isc_mutex_destroy(&quota->cblock);
+}
+
+void
+isc_quota_soft(isc_quota_t *quota, unsigned int soft) {
+ REQUIRE(VALID_QUOTA(quota));
+ atomic_store_release(&quota->soft, soft);
+}
+
+void
+isc_quota_max(isc_quota_t *quota, unsigned int max) {
+ REQUIRE(VALID_QUOTA(quota));
+ atomic_store_release(&quota->max, max);
+}
+
+unsigned int
+isc_quota_getmax(isc_quota_t *quota) {
+ REQUIRE(VALID_QUOTA(quota));
+ return (atomic_load_relaxed(&quota->max));
+}
+
+unsigned int
+isc_quota_getsoft(isc_quota_t *quota) {
+ REQUIRE(VALID_QUOTA(quota));
+ return (atomic_load_relaxed(&quota->soft));
+}
+
+unsigned int
+isc_quota_getused(isc_quota_t *quota) {
+ REQUIRE(VALID_QUOTA(quota));
+ return (atomic_load_relaxed(&quota->used));
+}
+
+static isc_result_t
+quota_reserve(isc_quota_t *quota) {
+ isc_result_t result;
+ uint_fast32_t max = atomic_load_acquire(&quota->max);
+ uint_fast32_t soft = atomic_load_acquire(&quota->soft);
+ uint_fast32_t used = atomic_load_acquire(&quota->used);
+ do {
+ if (max != 0 && used >= max) {
+ return (ISC_R_QUOTA);
+ }
+ if (soft != 0 && used >= soft) {
+ result = ISC_R_SOFTQUOTA;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ } while (!atomic_compare_exchange_weak_acq_rel(&quota->used, &used,
+ used + 1));
+ return (result);
+}
+
+/* Must be quota->cbslock locked */
+static void
+enqueue(isc_quota_t *quota, isc_quota_cb_t *cb) {
+ REQUIRE(cb != NULL);
+ ISC_LIST_ENQUEUE(quota->cbs, cb, link);
+ atomic_fetch_add_release(&quota->waiting, 1);
+}
+
+/* Must be quota->cbslock locked */
+static isc_quota_cb_t *
+dequeue(isc_quota_t *quota) {
+ isc_quota_cb_t *cb = ISC_LIST_HEAD(quota->cbs);
+ INSIST(cb != NULL);
+ ISC_LIST_DEQUEUE(quota->cbs, cb, link);
+ atomic_fetch_sub_relaxed(&quota->waiting, 1);
+ return (cb);
+}
+
+static void
+quota_release(isc_quota_t *quota) {
+ uint_fast32_t used;
+
+ /*
+ * This is opportunistic - we might race with a failing quota_attach_cb
+ * and not detect that something is waiting, but eventually someone will
+ * be releasing quota and will detect it, so we don't need to worry -
+ * and we're saving a lot by not locking cblock every time.
+ */
+
+ if (atomic_load_acquire(&quota->waiting) > 0) {
+ isc_quota_cb_t *cb = NULL;
+ LOCK(&quota->cblock);
+ if (atomic_load_relaxed(&quota->waiting) > 0) {
+ cb = dequeue(quota);
+ }
+ UNLOCK(&quota->cblock);
+ if (cb != NULL) {
+ cb->cb_func(quota, cb->data);
+ return;
+ }
+ }
+
+ used = atomic_fetch_sub_release(&quota->used, 1);
+ INSIST(used > 0);
+}
+
+static isc_result_t
+doattach(isc_quota_t *quota, isc_quota_t **p) {
+ isc_result_t result;
+ REQUIRE(p != NULL && *p == NULL);
+
+ result = quota_reserve(quota);
+ if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
+ *p = quota;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_quota_attach(isc_quota_t *quota, isc_quota_t **quotap) {
+ REQUIRE(VALID_QUOTA(quota));
+ REQUIRE(quotap != NULL && *quotap == NULL);
+
+ return (isc_quota_attach_cb(quota, quotap, NULL));
+}
+
+isc_result_t
+isc_quota_attach_cb(isc_quota_t *quota, isc_quota_t **quotap,
+ isc_quota_cb_t *cb) {
+ REQUIRE(VALID_QUOTA(quota));
+ REQUIRE(cb == NULL || VALID_QUOTA_CB(cb));
+ REQUIRE(quotap != NULL && *quotap == NULL);
+
+ isc_result_t result = doattach(quota, quotap);
+ if (result == ISC_R_QUOTA && cb != NULL) {
+ LOCK(&quota->cblock);
+ enqueue(quota, cb);
+ UNLOCK(&quota->cblock);
+ }
+ return (result);
+}
+
+void
+isc_quota_cb_init(isc_quota_cb_t *cb, isc_quota_cb_func_t cb_func, void *data) {
+ ISC_LINK_INIT(cb, link);
+ cb->cb_func = cb_func;
+ cb->data = data;
+ cb->magic = QUOTA_CB_MAGIC;
+}
+
+void
+isc_quota_detach(isc_quota_t **quotap) {
+ REQUIRE(quotap != NULL && VALID_QUOTA(*quotap));
+ isc_quota_t *quota = *quotap;
+ *quotap = NULL;
+
+ quota_release(quota);
+}
diff --git a/lib/isc/radix.c b/lib/isc/radix.c
new file mode 100644
index 0000000..54c0383
--- /dev/null
+++ b/lib/isc/radix.c
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 source was adapted from MRT's RCS Ids:
+ * Id: radix.c,v 1.10.2.1 1999/11/29 05:16:24 masaki Exp
+ * Id: prefix.c,v 1.37.2.9 2000/03/10 02:53:19 labovit Exp
+ */
+
+#include <inttypes.h>
+
+#include <isc/mem.h>
+#include <isc/radix.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#define BIT_TEST(f, b) (((f) & (b)) != 0)
+
+static isc_result_t
+_new_prefix(isc_mem_t *mctx, isc_prefix_t **target, int family, void *dest,
+ int bitlen);
+
+static void
+_deref_prefix(isc_prefix_t *prefix);
+
+static isc_result_t
+_ref_prefix(isc_mem_t *mctx, isc_prefix_t **target, isc_prefix_t *prefix);
+
+static int
+_comp_with_mask(void *addr, void *dest, u_int mask);
+
+static void
+_clear_radix(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func);
+
+static isc_result_t
+_new_prefix(isc_mem_t *mctx, isc_prefix_t **target, int family, void *dest,
+ int bitlen) {
+ isc_prefix_t *prefix;
+
+ REQUIRE(target != NULL);
+
+ if (family != AF_INET6 && family != AF_INET && family != AF_UNSPEC) {
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ prefix = isc_mem_get(mctx, sizeof(isc_prefix_t));
+
+ if (family == AF_INET6) {
+ prefix->bitlen = (bitlen >= 0) ? bitlen : 128;
+ memmove(&prefix->add.sin6, dest, 16);
+ } else {
+ /* AF_UNSPEC is "any" or "none"--treat it as AF_INET */
+ prefix->bitlen = (bitlen >= 0) ? bitlen : 32;
+ memmove(&prefix->add.sin, dest, 4);
+ }
+
+ prefix->family = family;
+ prefix->mctx = NULL;
+ isc_mem_attach(mctx, &prefix->mctx);
+
+ isc_refcount_init(&prefix->refcount, 1);
+
+ *target = prefix;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+_deref_prefix(isc_prefix_t *prefix) {
+ if (prefix != NULL) {
+ if (isc_refcount_decrement(&prefix->refcount) == 1) {
+ isc_refcount_destroy(&prefix->refcount);
+ isc_mem_putanddetach(&prefix->mctx, prefix,
+ sizeof(isc_prefix_t));
+ }
+ }
+}
+
+static isc_result_t
+_ref_prefix(isc_mem_t *mctx, isc_prefix_t **target, isc_prefix_t *prefix) {
+ INSIST(prefix != NULL);
+ INSIST((prefix->family == AF_INET && prefix->bitlen <= 32) ||
+ (prefix->family == AF_INET6 && prefix->bitlen <= 128) ||
+ (prefix->family == AF_UNSPEC && prefix->bitlen == 0));
+ REQUIRE(target != NULL && *target == NULL);
+
+ /*
+ * If this prefix is a static allocation, copy it into new memory.
+ * (Note, the refcount still has to be destroyed by the calling
+ * routine.)
+ */
+ if (isc_refcount_current(&prefix->refcount) == 0) {
+ isc_result_t ret;
+ ret = _new_prefix(mctx, target, prefix->family, &prefix->add,
+ prefix->bitlen);
+ return (ret);
+ }
+
+ isc_refcount_increment(&prefix->refcount);
+
+ *target = prefix;
+ return (ISC_R_SUCCESS);
+}
+
+static int
+_comp_with_mask(void *addr, void *dest, u_int mask) {
+ /* Mask length of zero matches everything */
+ if (mask == 0) {
+ return (1);
+ }
+
+ if (memcmp(addr, dest, mask / 8) == 0) {
+ u_int n = mask / 8;
+ u_int m = ((~0U) << (8 - (mask % 8)));
+
+ if ((mask % 8) == 0 ||
+ (((u_char *)addr)[n] & m) == (((u_char *)dest)[n] & m))
+ {
+ return (1);
+ }
+ }
+ return (0);
+}
+
+isc_result_t
+isc_radix_create(isc_mem_t *mctx, isc_radix_tree_t **target, int maxbits) {
+ isc_radix_tree_t *radix;
+
+ REQUIRE(target != NULL && *target == NULL);
+
+ radix = isc_mem_get(mctx, sizeof(isc_radix_tree_t));
+
+ radix->mctx = NULL;
+ isc_mem_attach(mctx, &radix->mctx);
+ radix->maxbits = maxbits;
+ radix->head = NULL;
+ radix->num_active_node = 0;
+ radix->num_added_node = 0;
+ RUNTIME_CHECK(maxbits <= RADIX_MAXBITS); /* XXX */
+ radix->magic = RADIX_TREE_MAGIC;
+ *target = radix;
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * if func is supplied, it will be called as func(node->data)
+ * before deleting the node
+ */
+
+static void
+_clear_radix(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func) {
+ REQUIRE(radix != NULL);
+
+ if (radix->head != NULL) {
+ isc_radix_node_t *Xstack[RADIX_MAXBITS + 1];
+ isc_radix_node_t **Xsp = Xstack;
+ isc_radix_node_t *Xrn = radix->head;
+
+ while (Xrn != NULL) {
+ isc_radix_node_t *l = Xrn->l;
+ isc_radix_node_t *r = Xrn->r;
+
+ if (Xrn->prefix != NULL) {
+ _deref_prefix(Xrn->prefix);
+ if (func != NULL) {
+ func(Xrn->data);
+ }
+ } else {
+ INSIST(Xrn->data[RADIX_V4] == NULL &&
+ Xrn->data[RADIX_V6] == NULL);
+ }
+
+ isc_mem_put(radix->mctx, Xrn, sizeof(*Xrn));
+ radix->num_active_node--;
+
+ if (l != NULL) {
+ if (r != NULL) {
+ *Xsp++ = r;
+ }
+ Xrn = l;
+ } else if (r != NULL) {
+ Xrn = r;
+ } else if (Xsp != Xstack) {
+ Xrn = *(--Xsp);
+ } else {
+ Xrn = NULL;
+ }
+ }
+ }
+ RUNTIME_CHECK(radix->num_active_node == 0);
+}
+
+void
+isc_radix_destroy(isc_radix_tree_t *radix, isc_radix_destroyfunc_t func) {
+ REQUIRE(radix != NULL);
+ _clear_radix(radix, func);
+ isc_mem_putanddetach(&radix->mctx, radix, sizeof(*radix));
+}
+
+/*
+ * func will be called as func(node->prefix, node->data)
+ */
+void
+isc_radix_process(isc_radix_tree_t *radix, isc_radix_processfunc_t func) {
+ isc_radix_node_t *node;
+
+ REQUIRE(func != NULL);
+
+ RADIX_WALK(radix->head, node) { func(node->prefix, node->data); }
+ RADIX_WALK_END;
+}
+
+isc_result_t
+isc_radix_search(isc_radix_tree_t *radix, isc_radix_node_t **target,
+ isc_prefix_t *prefix) {
+ isc_radix_node_t *node;
+ isc_radix_node_t *stack[RADIX_MAXBITS + 1];
+ u_char *addr;
+ uint32_t bitlen;
+ int tfam = -1, cnt = 0;
+
+ REQUIRE(radix != NULL);
+ REQUIRE(prefix != NULL);
+ REQUIRE(target != NULL && *target == NULL);
+ RUNTIME_CHECK(prefix->bitlen <= radix->maxbits);
+
+ *target = NULL;
+
+ node = radix->head;
+
+ if (node == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ addr = isc_prefix_touchar(prefix);
+ bitlen = prefix->bitlen;
+
+ while (node != NULL && node->bit < bitlen) {
+ if (node->prefix) {
+ stack[cnt++] = node;
+ }
+
+ if (BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))
+ {
+ node = node->r;
+ } else {
+ node = node->l;
+ }
+ }
+
+ if (node != NULL && node->prefix) {
+ stack[cnt++] = node;
+ }
+
+ while (cnt-- > 0) {
+ node = stack[cnt];
+
+ if (prefix->bitlen < node->bit) {
+ continue;
+ }
+
+ if (_comp_with_mask(isc_prefix_tochar(node->prefix),
+ isc_prefix_tochar(prefix),
+ node->prefix->bitlen))
+ {
+ int fam = ISC_RADIX_FAMILY(prefix);
+ if (node->node_num[fam] != -1 &&
+ ((*target == NULL) ||
+ (*target)->node_num[tfam] > node->node_num[fam]))
+ {
+ *target = node;
+ tfam = fam;
+ }
+ }
+ }
+
+ if (*target == NULL) {
+ return (ISC_R_NOTFOUND);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+isc_result_t
+isc_radix_insert(isc_radix_tree_t *radix, isc_radix_node_t **target,
+ isc_radix_node_t *source, isc_prefix_t *prefix) {
+ isc_radix_node_t *node, *new_node, *parent, *glue = NULL;
+ u_char *addr, *test_addr;
+ uint32_t bitlen, fam, check_bit, differ_bit;
+ uint32_t i, j, r;
+ isc_result_t result;
+
+ REQUIRE(radix != NULL);
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(prefix != NULL || (source != NULL && source->prefix != NULL));
+ RUNTIME_CHECK(prefix == NULL || prefix->bitlen <= radix->maxbits);
+
+ if (prefix == NULL) {
+ prefix = source->prefix;
+ }
+
+ INSIST(prefix != NULL);
+
+ bitlen = prefix->bitlen;
+ fam = prefix->family;
+
+ if (radix->head == NULL) {
+ node = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t));
+ node->bit = bitlen;
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ node->node_num[i] = -1;
+ }
+ node->prefix = NULL;
+ result = _ref_prefix(radix->mctx, &node->prefix, prefix);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(radix->mctx, node,
+ sizeof(isc_radix_node_t));
+ return (result);
+ }
+ node->parent = NULL;
+ node->l = node->r = NULL;
+ if (source != NULL) {
+ /*
+ * If source is non-NULL, then we're merging in a
+ * node from an existing radix tree. To keep
+ * the node_num values consistent, the calling
+ * function will add the total number of nodes
+ * added to num_added_node at the end of
+ * the merge operation--we don't do it here.
+ */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ if (source->node_num[i] != -1) {
+ node->node_num[i] =
+ radix->num_added_node +
+ source->node_num[i];
+ }
+ node->data[i] = source->data[i];
+ }
+ } else {
+ int next = ++radix->num_added_node;
+ if (fam == AF_UNSPEC) {
+ /* "any" or "none" */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ node->node_num[i] = next;
+ }
+ } else {
+ node->node_num[ISC_RADIX_FAMILY(prefix)] = next;
+ }
+
+ memset(node->data, 0, sizeof(node->data));
+ }
+ radix->head = node;
+ radix->num_active_node++;
+ *target = node;
+ return (ISC_R_SUCCESS);
+ }
+
+ addr = isc_prefix_touchar(prefix);
+ node = radix->head;
+
+ while (node->bit < bitlen || node->prefix == NULL) {
+ if (node->bit < radix->maxbits &&
+ BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))
+ {
+ if (node->r == NULL) {
+ break;
+ }
+ node = node->r;
+ } else {
+ if (node->l == NULL) {
+ break;
+ }
+ node = node->l;
+ }
+
+ INSIST(node != NULL);
+ }
+
+ INSIST(node->prefix != NULL);
+
+ test_addr = isc_prefix_touchar(node->prefix);
+ /* Find the first bit different. */
+ check_bit = (node->bit < bitlen) ? node->bit : bitlen;
+ differ_bit = 0;
+ for (i = 0; i * 8 < check_bit; i++) {
+ if ((r = (addr[i] ^ test_addr[i])) == 0) {
+ differ_bit = (i + 1) * 8;
+ continue;
+ }
+ /* I know the better way, but for now. */
+ for (j = 0; j < 8; j++) {
+ if (BIT_TEST(r, (0x80 >> j))) {
+ break;
+ }
+ }
+ /* Must be found. */
+ INSIST(j < 8);
+ differ_bit = i * 8 + j;
+ break;
+ }
+
+ if (differ_bit > check_bit) {
+ differ_bit = check_bit;
+ }
+
+ parent = node->parent;
+ while (parent != NULL && parent->bit >= differ_bit) {
+ node = parent;
+ parent = node->parent;
+ }
+
+ if (differ_bit == bitlen && node->bit == bitlen) {
+ if (node->prefix != NULL) {
+ /* Set node_num only if it hasn't been set before */
+ if (source != NULL) {
+ /* Merging nodes */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ if (node->node_num[i] == -1 &&
+ source->node_num[i] != -1)
+ {
+ node->node_num[i] =
+ radix->num_added_node +
+ source->node_num[i];
+ node->data[i] = source->data[i];
+ }
+ }
+ } else {
+ if (fam == AF_UNSPEC) {
+ /* "any" or "none" */
+ int next = radix->num_added_node + 1;
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ if (node->node_num[i] == -1) {
+ node->node_num[i] =
+ next;
+ radix->num_added_node =
+ next;
+ }
+ }
+ } else {
+ int foff = ISC_RADIX_FAMILY(prefix);
+ if (node->node_num[foff] == -1) {
+ node->node_num[foff] =
+ ++radix->num_added_node;
+ }
+ }
+ }
+ *target = node;
+ return (ISC_R_SUCCESS);
+ } else {
+ result = _ref_prefix(radix->mctx, &node->prefix,
+ prefix);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ INSIST(node->data[RADIX_V4] == NULL &&
+ node->node_num[RADIX_V4] == -1 &&
+ node->data[RADIX_V4] == NULL &&
+ node->node_num[RADIX_V4] == -1);
+ if (source != NULL) {
+ /* Merging node */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ int cur = radix->num_added_node;
+ if (source->node_num[i] != -1) {
+ node->node_num[i] =
+ source->node_num[i] + cur;
+ node->data[i] = source->data[i];
+ }
+ }
+ } else {
+ int next = ++radix->num_added_node;
+ if (fam == AF_UNSPEC) {
+ /* "any" or "none" */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ node->node_num[i] = next;
+ }
+ } else {
+ node->node_num[ISC_RADIX_FAMILY(prefix)] = next;
+ }
+ }
+ *target = node;
+ return (ISC_R_SUCCESS);
+ }
+
+ new_node = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t));
+ if (node->bit != differ_bit && bitlen != differ_bit) {
+ glue = isc_mem_get(radix->mctx, sizeof(isc_radix_node_t));
+ }
+ new_node->bit = bitlen;
+ new_node->prefix = NULL;
+ result = _ref_prefix(radix->mctx, &new_node->prefix, prefix);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(radix->mctx, new_node, sizeof(isc_radix_node_t));
+ if (glue != NULL) {
+ isc_mem_put(radix->mctx, glue,
+ sizeof(isc_radix_node_t));
+ }
+ return (result);
+ }
+ new_node->parent = NULL;
+ new_node->l = new_node->r = NULL;
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ new_node->node_num[i] = -1;
+ new_node->data[i] = NULL;
+ }
+ radix->num_active_node++;
+
+ if (source != NULL) {
+ /* Merging node */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ int cur = radix->num_added_node;
+ if (source->node_num[i] != -1) {
+ new_node->node_num[i] = source->node_num[i] +
+ cur;
+ new_node->data[i] = source->data[i];
+ }
+ }
+ } else {
+ int next = ++radix->num_added_node;
+ if (fam == AF_UNSPEC) {
+ /* "any" or "none" */
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ new_node->node_num[i] = next;
+ }
+ } else {
+ new_node->node_num[ISC_RADIX_FAMILY(prefix)] = next;
+ }
+ memset(new_node->data, 0, sizeof(new_node->data));
+ }
+
+ if (node->bit == differ_bit) {
+ INSIST(glue == NULL);
+ new_node->parent = node;
+ if (node->bit < radix->maxbits &&
+ BIT_TEST(addr[node->bit >> 3], 0x80 >> (node->bit & 0x07)))
+ {
+ INSIST(node->r == NULL);
+ node->r = new_node;
+ } else {
+ INSIST(node->l == NULL);
+ node->l = new_node;
+ }
+ *target = new_node;
+ return (ISC_R_SUCCESS);
+ }
+
+ if (bitlen == differ_bit) {
+ INSIST(glue == NULL);
+ if (bitlen < radix->maxbits &&
+ BIT_TEST(test_addr[bitlen >> 3], 0x80 >> (bitlen & 0x07)))
+ {
+ new_node->r = node;
+ } else {
+ new_node->l = node;
+ }
+ new_node->parent = node->parent;
+ if (node->parent == NULL) {
+ INSIST(radix->head == node);
+ radix->head = new_node;
+ } else if (node->parent->r == node) {
+ node->parent->r = new_node;
+ } else {
+ node->parent->l = new_node;
+ }
+ node->parent = new_node;
+ } else {
+ INSIST(glue != NULL);
+ glue->bit = differ_bit;
+ glue->prefix = NULL;
+ glue->parent = node->parent;
+ for (i = 0; i < RADIX_FAMILIES; i++) {
+ glue->data[i] = NULL;
+ glue->node_num[i] = -1;
+ }
+ radix->num_active_node++;
+ if (differ_bit < radix->maxbits &&
+ BIT_TEST(addr[differ_bit >> 3], 0x80 >> (differ_bit & 07)))
+ {
+ glue->r = new_node;
+ glue->l = node;
+ } else {
+ glue->r = node;
+ glue->l = new_node;
+ }
+ new_node->parent = glue;
+
+ if (node->parent == NULL) {
+ INSIST(radix->head == node);
+ radix->head = glue;
+ } else if (node->parent->r == node) {
+ node->parent->r = glue;
+ } else {
+ node->parent->l = glue;
+ }
+ node->parent = glue;
+ }
+
+ *target = new_node;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_radix_remove(isc_radix_tree_t *radix, isc_radix_node_t *node) {
+ isc_radix_node_t *parent, *child;
+
+ REQUIRE(radix != NULL);
+ REQUIRE(node != NULL);
+
+ if (node->r && node->l) {
+ /*
+ * This might be a placeholder node -- have to check and
+ * make sure there is a prefix associated with it!
+ */
+ if (node->prefix != NULL) {
+ _deref_prefix(node->prefix);
+ }
+
+ node->prefix = NULL;
+ memset(node->data, 0, sizeof(node->data));
+ return;
+ }
+
+ if (node->r == NULL && node->l == NULL) {
+ parent = node->parent;
+ _deref_prefix(node->prefix);
+
+ if (parent == NULL) {
+ INSIST(radix->head == node);
+ radix->head = NULL;
+ isc_mem_put(radix->mctx, node, sizeof(*node));
+ radix->num_active_node--;
+ return;
+ }
+
+ if (parent->r == node) {
+ parent->r = NULL;
+ child = parent->l;
+ } else {
+ INSIST(parent->l == node);
+ parent->l = NULL;
+ child = parent->r;
+ }
+
+ isc_mem_put(radix->mctx, node, sizeof(*node));
+ radix->num_active_node--;
+
+ if (parent->prefix) {
+ return;
+ }
+
+ /* We need to remove parent too. */
+ if (parent->parent == NULL) {
+ INSIST(radix->head == parent);
+ radix->head = child;
+ } else if (parent->parent->r == parent) {
+ parent->parent->r = child;
+ } else {
+ INSIST(parent->parent->l == parent);
+ parent->parent->l = child;
+ }
+
+ child->parent = parent->parent;
+ isc_mem_put(radix->mctx, parent, sizeof(*parent));
+ radix->num_active_node--;
+ return;
+ }
+
+ if (node->r) {
+ child = node->r;
+ } else {
+ INSIST(node->l != NULL);
+ child = node->l;
+ }
+
+ parent = node->parent;
+ child->parent = parent;
+
+ _deref_prefix(node->prefix);
+
+ if (parent == NULL) {
+ INSIST(radix->head == node);
+ radix->head = child;
+ isc_mem_put(radix->mctx, node, sizeof(*node));
+ radix->num_active_node--;
+ return;
+ }
+
+ if (parent->r == node) {
+ parent->r = child;
+ } else {
+ INSIST(parent->l == node);
+ parent->l = child;
+ }
+
+ isc_mem_put(radix->mctx, node, sizeof(*node));
+ radix->num_active_node--;
+}
diff --git a/lib/isc/random.c b/lib/isc/random.c
new file mode 100644
index 0000000..7eead66
--- /dev/null
+++ b/lib/isc/random.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 of isc_random_uniform():
+ *
+ * Copyright (c) 1996, David Mazieres <dm@uun.org>
+ * Copyright (c) 2008, Damien Miller <djm@openbsd.org>
+ *
+ * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <isc/once.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/thread.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include "entropy_private.h"
+
+/*
+ * The specific implementation for PRNG is included as a C file
+ * that has to provide a static variable named seed, and a function
+ * uint32_t next(void) that provides next random number.
+ *
+ * The implementation must be thread-safe.
+ */
+
+/*
+ * Two contestants have been considered: the xoroshiro family of the
+ * functions by Villa&Blackman, and PCG by O'Neill. After
+ * consideration, the xoshiro128starstar function has been chosen as
+ * the uint32_t random number provider because it is very fast and has
+ * good enough properties for our usage pattern.
+ */
+
+/*
+ * Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)
+ *
+ * To the extent possible under law, the author has dedicated all
+ * copyright and related and neighboring rights to this software to the
+ * public domain worldwide. This software is distributed without any
+ * warranty.
+ *
+ * See <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+/*
+ * This is xoshiro128** 1.0, our 32-bit all-purpose, rock-solid generator.
+ * It has excellent (sub-ns) speed, a state size (128 bits) that is large
+ * enough for mild parallelism, and it passes all tests we are aware of.
+ *
+ * For generating just single-precision (i.e., 32-bit) floating-point
+ * numbers, xoshiro128+ is even faster.
+ *
+ * The state must be seeded so that it is not everywhere zero.
+ */
+static thread_local uint32_t seed[4] = { 0 };
+
+static uint32_t
+rotl(const uint32_t x, int k) {
+ return ((x << k) | (x >> (32 - k)));
+}
+
+static uint32_t
+next(void) {
+ uint32_t result_starstar, t;
+
+ result_starstar = rotl(seed[0] * 5, 7) * 9;
+ t = seed[1] << 9;
+
+ seed[2] ^= seed[0];
+ seed[3] ^= seed[1];
+ seed[1] ^= seed[2];
+ seed[0] ^= seed[3];
+
+ seed[2] ^= t;
+
+ seed[3] = rotl(seed[3], 11);
+
+ return (result_starstar);
+}
+
+static thread_local isc_once_t isc_random_once = ISC_ONCE_INIT;
+
+static void
+isc_random_initialize(void) {
+ int useed[4] = { 0, 0, 0, 1 };
+#if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ /*
+ * Set a constant seed to help in problem reproduction should fuzzing
+ * find a crash or a hang. The seed array must be non-zero else
+ * xoshiro128starstar will generate an infinite series of zeroes.
+ */
+#else /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+ isc_entropy_get(useed, sizeof(useed));
+#endif /* if FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION */
+ memmove(seed, useed, sizeof(seed));
+}
+
+uint8_t
+isc_random8(void) {
+ RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
+ ISC_R_SUCCESS);
+ return (next() & 0xff);
+}
+
+uint16_t
+isc_random16(void) {
+ RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
+ ISC_R_SUCCESS);
+ return (next() & 0xffff);
+}
+
+uint32_t
+isc_random32(void) {
+ RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
+ ISC_R_SUCCESS);
+ return (next());
+}
+
+void
+isc_random_buf(void *buf, size_t buflen) {
+ int i;
+ uint32_t r;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(buflen > 0);
+
+ RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
+ ISC_R_SUCCESS);
+
+ for (i = 0; i + sizeof(r) <= buflen; i += sizeof(r)) {
+ r = next();
+ memmove((uint8_t *)buf + i, &r, sizeof(r));
+ }
+ r = next();
+ memmove((uint8_t *)buf + i, &r, buflen % sizeof(r));
+ return;
+}
+
+uint32_t
+isc_random_uniform(uint32_t upper_bound) {
+ /* Copy of arc4random_uniform from OpenBSD */
+ uint32_t r, min;
+
+ RUNTIME_CHECK(isc_once_do(&isc_random_once, isc_random_initialize) ==
+ ISC_R_SUCCESS);
+
+ if (upper_bound < 2) {
+ return (0);
+ }
+
+#if (ULONG_MAX > 0xffffffffUL)
+ min = 0x100000000UL % upper_bound;
+#else /* if (ULONG_MAX > 0xffffffffUL) */
+ /* Calculate (2**32 % upper_bound) avoiding 64-bit math */
+ if (upper_bound > 0x80000000) {
+ min = 1 + ~upper_bound; /* 2**32 - upper_bound */
+ } else {
+ /* (2**32 - (x * 2)) % x == 2**32 % x when x <= 2**31 */
+ min = ((0xffffffff - (upper_bound * 2)) + 1) % upper_bound;
+ }
+#endif /* if (ULONG_MAX > 0xffffffffUL) */
+
+ /*
+ * This could theoretically loop forever but each retry has
+ * p > 0.5 (worst case, usually far better) of selecting a
+ * number inside the range we need, so it should rarely need
+ * to re-roll.
+ */
+ for (;;) {
+ r = next();
+ if (r >= min) {
+ break;
+ }
+ }
+
+ return (r % upper_bound);
+}
diff --git a/lib/isc/ratelimiter.c b/lib/isc/ratelimiter.c
new file mode 100644
index 0000000..84a848a
--- /dev/null
+++ b/lib/isc/ratelimiter.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.
+ */
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/ratelimiter.h>
+#include <isc/refcount.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+typedef enum {
+ isc_ratelimiter_stalled = 0,
+ isc_ratelimiter_ratelimited = 1,
+ isc_ratelimiter_idle = 2,
+ isc_ratelimiter_shuttingdown = 3
+} isc_ratelimiter_state_t;
+
+struct isc_ratelimiter {
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ isc_refcount_t references;
+ isc_task_t *task;
+ isc_timer_t *timer;
+ isc_interval_t interval;
+ uint32_t pertic;
+ bool pushpop;
+ isc_ratelimiter_state_t state;
+ isc_event_t shutdownevent;
+ ISC_LIST(isc_event_t) pending;
+};
+
+#define ISC_RATELIMITEREVENT_SHUTDOWN (ISC_EVENTCLASS_RATELIMITER + 1)
+
+static void
+ratelimiter_tick(isc_task_t *task, isc_event_t *event);
+
+static void
+ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event);
+
+isc_result_t
+isc_ratelimiter_create(isc_mem_t *mctx, isc_timermgr_t *timermgr,
+ isc_task_t *task, isc_ratelimiter_t **ratelimiterp) {
+ isc_result_t result;
+ isc_ratelimiter_t *rl;
+ INSIST(ratelimiterp != NULL && *ratelimiterp == NULL);
+
+ rl = isc_mem_get(mctx, sizeof(*rl));
+ *rl = (isc_ratelimiter_t){
+ .mctx = mctx,
+ .task = task,
+ .pertic = 1,
+ .state = isc_ratelimiter_idle,
+ };
+
+ isc_refcount_init(&rl->references, 1);
+ isc_interval_set(&rl->interval, 0, 0);
+ ISC_LIST_INIT(rl->pending);
+
+ isc_mutex_init(&rl->lock);
+
+ result = isc_timer_create(timermgr, isc_timertype_inactive, NULL, NULL,
+ rl->task, ratelimiter_tick, rl, &rl->timer);
+ if (result != ISC_R_SUCCESS) {
+ goto free_mutex;
+ }
+
+ /*
+ * Increment the reference count to indicate that we may
+ * (soon) have events outstanding.
+ */
+ isc_refcount_increment(&rl->references);
+
+ ISC_EVENT_INIT(&rl->shutdownevent, sizeof(isc_event_t), 0, NULL,
+ ISC_RATELIMITEREVENT_SHUTDOWN,
+ ratelimiter_shutdowncomplete, rl, rl, NULL, NULL);
+
+ *ratelimiterp = rl;
+ return (ISC_R_SUCCESS);
+
+free_mutex:
+ isc_refcount_decrementz(&rl->references);
+ isc_refcount_destroy(&rl->references);
+ isc_mutex_destroy(&rl->lock);
+ isc_mem_put(mctx, rl, sizeof(*rl));
+ return (result);
+}
+
+isc_result_t
+isc_ratelimiter_setinterval(isc_ratelimiter_t *rl, isc_interval_t *interval) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rl != NULL);
+ REQUIRE(interval != NULL);
+
+ LOCK(&rl->lock);
+ rl->interval = *interval;
+ /*
+ * If the timer is currently running, change its rate.
+ */
+ if (rl->state == isc_ratelimiter_ratelimited) {
+ result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL,
+ &rl->interval, false);
+ }
+ UNLOCK(&rl->lock);
+ return (result);
+}
+
+void
+isc_ratelimiter_setpertic(isc_ratelimiter_t *rl, uint32_t pertic) {
+ REQUIRE(rl != NULL);
+
+ if (pertic == 0) {
+ pertic = 1;
+ }
+ rl->pertic = pertic;
+}
+
+void
+isc_ratelimiter_setpushpop(isc_ratelimiter_t *rl, bool pushpop) {
+ REQUIRE(rl != NULL);
+
+ rl->pushpop = pushpop;
+}
+
+isc_result_t
+isc_ratelimiter_enqueue(isc_ratelimiter_t *rl, isc_task_t *task,
+ isc_event_t **eventp) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_event_t *ev;
+
+ REQUIRE(rl != NULL);
+ REQUIRE(task != NULL);
+ REQUIRE(eventp != NULL && *eventp != NULL);
+ ev = *eventp;
+ REQUIRE(ev->ev_sender == NULL);
+
+ LOCK(&rl->lock);
+ if (rl->state == isc_ratelimiter_ratelimited ||
+ rl->state == isc_ratelimiter_stalled)
+ {
+ ev->ev_sender = task;
+ *eventp = NULL;
+ if (rl->pushpop) {
+ ISC_LIST_PREPEND(rl->pending, ev, ev_ratelink);
+ } else {
+ ISC_LIST_APPEND(rl->pending, ev, ev_ratelink);
+ }
+ } else if (rl->state == isc_ratelimiter_idle) {
+ result = isc_timer_reset(rl->timer, isc_timertype_ticker, NULL,
+ &rl->interval, false);
+ if (result == ISC_R_SUCCESS) {
+ ev->ev_sender = task;
+ rl->state = isc_ratelimiter_ratelimited;
+ }
+ } else {
+ INSIST(rl->state == isc_ratelimiter_shuttingdown);
+ result = ISC_R_SHUTTINGDOWN;
+ }
+ UNLOCK(&rl->lock);
+ if (*eventp != NULL && result == ISC_R_SUCCESS) {
+ isc_task_send(task, eventp);
+ }
+ return (result);
+}
+
+isc_result_t
+isc_ratelimiter_dequeue(isc_ratelimiter_t *rl, isc_event_t *event) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rl != NULL);
+ REQUIRE(event != NULL);
+
+ LOCK(&rl->lock);
+ if (ISC_LINK_LINKED(event, ev_ratelink)) {
+ ISC_LIST_UNLINK(rl->pending, event, ev_ratelink);
+ event->ev_sender = NULL;
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&rl->lock);
+ return (result);
+}
+
+static void
+ratelimiter_tick(isc_task_t *task, isc_event_t *event) {
+ isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
+ isc_event_t *p;
+ uint32_t pertic;
+
+ UNUSED(task);
+
+ isc_event_free(&event);
+
+ pertic = rl->pertic;
+ while (pertic != 0) {
+ pertic--;
+ LOCK(&rl->lock);
+ p = ISC_LIST_HEAD(rl->pending);
+ if (p != NULL) {
+ /*
+ * There is work to do. Let's do it after unlocking.
+ */
+ ISC_LIST_UNLINK(rl->pending, p, ev_ratelink);
+ } else {
+ /*
+ * No work left to do. Stop the timer so that we don't
+ * waste resources by having it fire periodically.
+ */
+ isc_result_t result = isc_timer_reset(
+ rl->timer, isc_timertype_inactive, NULL, NULL,
+ false);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ rl->state = isc_ratelimiter_idle;
+ pertic = 0; /* Force the loop to exit. */
+ }
+ UNLOCK(&rl->lock);
+ if (p != NULL) {
+ isc_task_t *evtask = p->ev_sender;
+ isc_task_send(evtask, &p);
+ }
+ INSIST(p == NULL);
+ }
+}
+
+void
+isc_ratelimiter_shutdown(isc_ratelimiter_t *rl) {
+ isc_event_t *ev;
+ isc_task_t *task;
+ isc_result_t result;
+
+ REQUIRE(rl != NULL);
+
+ LOCK(&rl->lock);
+ rl->state = isc_ratelimiter_shuttingdown;
+ (void)isc_timer_reset(rl->timer, isc_timertype_inactive, NULL, NULL,
+ false);
+ while ((ev = ISC_LIST_HEAD(rl->pending)) != NULL) {
+ task = ev->ev_sender;
+ ISC_LIST_UNLINK(rl->pending, ev, ev_ratelink);
+ ev->ev_attributes |= ISC_EVENTATTR_CANCELED;
+ isc_task_send(task, &ev);
+ }
+ task = NULL;
+ isc_task_attach(rl->task, &task);
+
+ result = isc_timer_reset(rl->timer, isc_timertype_inactive, NULL, NULL,
+ true);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_timer_destroy(&rl->timer);
+
+ /*
+ * Send an event to our task. The delivery of this event
+ * indicates that no more timer events will be delivered.
+ */
+ ev = &rl->shutdownevent;
+ isc_task_send(rl->task, &ev);
+
+ UNLOCK(&rl->lock);
+}
+
+static void
+ratelimiter_shutdowncomplete(isc_task_t *task, isc_event_t *event) {
+ isc_ratelimiter_t *rl = (isc_ratelimiter_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ isc_ratelimiter_detach(&rl);
+ isc_task_detach(&task);
+}
+
+static void
+ratelimiter_free(isc_ratelimiter_t *rl) {
+ isc_refcount_destroy(&rl->references);
+ isc_mutex_destroy(&rl->lock);
+ isc_mem_put(rl->mctx, rl, sizeof(*rl));
+}
+
+void
+isc_ratelimiter_attach(isc_ratelimiter_t *source, isc_ratelimiter_t **target) {
+ REQUIRE(source != NULL);
+ REQUIRE(target != NULL && *target == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *target = source;
+}
+
+void
+isc_ratelimiter_detach(isc_ratelimiter_t **rlp) {
+ isc_ratelimiter_t *rl;
+
+ REQUIRE(rlp != NULL && *rlp != NULL);
+
+ rl = *rlp;
+ *rlp = NULL;
+
+ if (isc_refcount_decrement(&rl->references) == 1) {
+ ratelimiter_free(rl);
+ }
+}
+
+isc_result_t
+isc_ratelimiter_stall(isc_ratelimiter_t *rl) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rl != NULL);
+
+ LOCK(&rl->lock);
+ switch (rl->state) {
+ case isc_ratelimiter_shuttingdown:
+ result = ISC_R_SHUTTINGDOWN;
+ break;
+ case isc_ratelimiter_ratelimited:
+ result = isc_timer_reset(rl->timer, isc_timertype_inactive,
+ NULL, NULL, false);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ FALLTHROUGH;
+ case isc_ratelimiter_idle:
+ case isc_ratelimiter_stalled:
+ rl->state = isc_ratelimiter_stalled;
+ break;
+ }
+ UNLOCK(&rl->lock);
+ return (result);
+}
+
+isc_result_t
+isc_ratelimiter_release(isc_ratelimiter_t *rl) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(rl != NULL);
+
+ LOCK(&rl->lock);
+ switch (rl->state) {
+ case isc_ratelimiter_shuttingdown:
+ result = ISC_R_SHUTTINGDOWN;
+ break;
+ case isc_ratelimiter_stalled:
+ if (!ISC_LIST_EMPTY(rl->pending)) {
+ result = isc_timer_reset(rl->timer,
+ isc_timertype_ticker, NULL,
+ &rl->interval, false);
+ if (result == ISC_R_SUCCESS) {
+ rl->state = isc_ratelimiter_ratelimited;
+ }
+ } else {
+ rl->state = isc_ratelimiter_idle;
+ }
+ break;
+ case isc_ratelimiter_ratelimited:
+ case isc_ratelimiter_idle:
+ break;
+ }
+ UNLOCK(&rl->lock);
+ return (result);
+}
diff --git a/lib/isc/regex.c b/lib/isc/regex.c
new file mode 100644
index 0000000..f7a3f5e
--- /dev/null
+++ b/lib/isc/regex.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/file.h>
+#include <isc/print.h>
+#include <isc/regex.h>
+#include <isc/string.h>
+
+#if VALREGEX_REPORT_REASON
+#define FAIL(x) \
+ do { \
+ reason = (x); \
+ goto error; \
+ } while (0)
+#else /* if VALREGEX_REPORT_REASON */
+#define FAIL(x) goto error
+#endif /* if VALREGEX_REPORT_REASON */
+
+/*
+ * Validate the regular expression 'C' locale.
+ */
+int
+isc_regex_validate(const char *c) {
+ enum {
+ none,
+ parse_bracket,
+ parse_bound,
+ parse_ce,
+ parse_ec,
+ parse_cc
+ } state = none;
+ /* Well known character classes. */
+ const char *cc[] = { ":alnum:", ":digit:", ":punct:", ":alpha:",
+ ":graph:", ":space:", ":blank:", ":lower:",
+ ":upper:", ":cntrl:", ":print:", ":xdigit:" };
+ bool seen_comma = false;
+ bool seen_high = false;
+ bool seen_char = false;
+ bool seen_ec = false;
+ bool seen_ce = false;
+ bool have_atom = false;
+ int group = 0;
+ int range = 0;
+ int sub = 0;
+ bool empty_ok = false;
+ bool neg = false;
+ bool was_multiple = false;
+ unsigned int low = 0;
+ unsigned int high = 0;
+ const char *ccname = NULL;
+ int range_start = 0;
+#if VALREGEX_REPORT_REASON
+ const char *reason = "";
+#endif /* if VALREGEX_REPORT_REASON */
+
+ if (c == NULL || *c == 0) {
+ FAIL("empty string");
+ }
+
+ while (c != NULL && *c != 0) {
+ switch (state) {
+ case none:
+ switch (*c) {
+ case '\\': /* make literal */
+ ++c;
+ switch (*c) {
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if ((*c - '0') > sub) {
+ FAIL("bad back reference");
+ }
+ have_atom = true;
+ was_multiple = false;
+ break;
+ case 0:
+ FAIL("escaped end-of-string");
+ default:
+ goto literal;
+ }
+ ++c;
+ break;
+ case '[': /* bracket start */
+ ++c;
+ neg = false;
+ was_multiple = false;
+ seen_char = false;
+ state = parse_bracket;
+ break;
+ case '{': /* bound start */
+ switch (c[1]) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (!have_atom) {
+ FAIL("no atom");
+ }
+ if (was_multiple) {
+ FAIL("was multiple");
+ }
+ seen_comma = false;
+ seen_high = false;
+ low = high = 0;
+ state = parse_bound;
+ break;
+ default:
+ goto literal;
+ }
+ ++c;
+ have_atom = true;
+ was_multiple = true;
+ break;
+ case '}':
+ goto literal;
+ case '(': /* group start */
+ have_atom = false;
+ was_multiple = false;
+ empty_ok = true;
+ ++group;
+ ++sub;
+ ++c;
+ break;
+ case ')': /* group end */
+ if (group && !have_atom && !empty_ok) {
+ FAIL("empty alternative");
+ }
+ have_atom = true;
+ was_multiple = false;
+ if (group != 0) {
+ --group;
+ }
+ ++c;
+ break;
+ case '|': /* alternative separator */
+ if (!have_atom) {
+ FAIL("no atom");
+ }
+ have_atom = false;
+ empty_ok = false;
+ was_multiple = false;
+ ++c;
+ break;
+ case '^':
+ case '$':
+ have_atom = true;
+ was_multiple = true;
+ ++c;
+ break;
+ case '+':
+ case '*':
+ case '?':
+ if (was_multiple) {
+ FAIL("was multiple");
+ }
+ if (!have_atom) {
+ FAIL("no atom");
+ }
+ have_atom = true;
+ was_multiple = true;
+ ++c;
+ break;
+ case '.':
+ default:
+ literal:
+ have_atom = true;
+ was_multiple = false;
+ ++c;
+ break;
+ }
+ break;
+ case parse_bound:
+ switch (*c) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (!seen_comma) {
+ low = low * 10 + *c - '0';
+ if (low > 255) {
+ FAIL("lower bound too big");
+ }
+ } else {
+ seen_high = true;
+ high = high * 10 + *c - '0';
+ if (high > 255) {
+ FAIL("upper bound too big");
+ }
+ }
+ ++c;
+ break;
+ case ',':
+ if (seen_comma) {
+ FAIL("multiple commas");
+ }
+ seen_comma = true;
+ ++c;
+ break;
+ default:
+ case '{':
+ FAIL("non digit/comma");
+ case '}':
+ if (seen_high && low > high) {
+ FAIL("bad parse bound");
+ }
+ seen_comma = false;
+ state = none;
+ ++c;
+ break;
+ }
+ break;
+ case parse_bracket:
+ switch (*c) {
+ case '^':
+ if (seen_char || neg) {
+ goto inside;
+ }
+ neg = true;
+ ++c;
+ break;
+ case '-':
+ if (range == 2) {
+ goto inside;
+ }
+ if (!seen_char) {
+ goto inside;
+ }
+ if (range == 1) {
+ FAIL("bad range");
+ }
+ range = 2;
+ ++c;
+ break;
+ case '[':
+ ++c;
+ switch (*c) {
+ case '.': /* collating element */
+ if (range != 0) {
+ --range;
+ }
+ ++c;
+ state = parse_ce;
+ seen_ce = false;
+ break;
+ case '=': /* equivalence class */
+ if (range == 2) {
+ FAIL("equivalence class in "
+ "range");
+ }
+ ++c;
+ state = parse_ec;
+ seen_ec = false;
+ break;
+ case ':': /* character class */
+ if (range == 2) {
+ FAIL("character class in "
+ "range");
+ }
+ ccname = c;
+ ++c;
+ state = parse_cc;
+ break;
+ }
+ seen_char = true;
+ break;
+ case ']':
+ if (!c[1] && !seen_char) {
+ FAIL("unfinished brace");
+ }
+ if (!seen_char) {
+ goto inside;
+ }
+ ++c;
+ range = 0;
+ have_atom = true;
+ state = none;
+ break;
+ default:
+ inside:
+ seen_char = true;
+ if (range == 2 && (*c & 0xff) < range_start) {
+ FAIL("out of order range");
+ }
+ if (range != 0) {
+ --range;
+ }
+ range_start = *c & 0xff;
+ ++c;
+ break;
+ }
+ break;
+ case parse_ce:
+ switch (*c) {
+ case '.':
+ ++c;
+ switch (*c) {
+ case ']':
+ if (!seen_ce) {
+ FAIL("empty ce");
+ }
+ ++c;
+ state = parse_bracket;
+ break;
+ default:
+ if (seen_ce) {
+ range_start = 256;
+ } else {
+ range_start = '.';
+ }
+ seen_ce = true;
+ break;
+ }
+ break;
+ default:
+ if (seen_ce) {
+ range_start = 256;
+ } else {
+ range_start = *c;
+ }
+ seen_ce = true;
+ ++c;
+ break;
+ }
+ break;
+ case parse_ec:
+ switch (*c) {
+ case '=':
+ ++c;
+ switch (*c) {
+ case ']':
+ if (!seen_ec) {
+ FAIL("no ec");
+ }
+ ++c;
+ state = parse_bracket;
+ break;
+ default:
+ seen_ec = true;
+ break;
+ }
+ break;
+ default:
+ seen_ec = true;
+ ++c;
+ break;
+ }
+ break;
+ case parse_cc:
+ switch (*c) {
+ case ':':
+ ++c;
+ switch (*c) {
+ case ']': {
+ unsigned int i;
+ bool found = false;
+ for (i = 0;
+ i < sizeof(cc) / sizeof(*cc); i++)
+ {
+ unsigned int len;
+ len = strlen(cc[i]);
+ if (len !=
+ (unsigned int)(c - ccname))
+ {
+ continue;
+ }
+ if (strncmp(cc[i], ccname, len))
+ {
+ continue;
+ }
+ found = true;
+ }
+ if (!found) {
+ FAIL("unknown cc");
+ }
+ ++c;
+ state = parse_bracket;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ default:
+ ++c;
+ break;
+ }
+ break;
+ }
+ }
+ if (group != 0) {
+ FAIL("group open");
+ }
+ if (state != none) {
+ FAIL("incomplete");
+ }
+ if (!have_atom) {
+ FAIL("no atom");
+ }
+ return (sub);
+
+error:
+#if VALREGEX_REPORT_REASON
+ fprintf(stderr, "%s\n", reason);
+#endif /* if VALREGEX_REPORT_REASON */
+ return (-1);
+}
diff --git a/lib/isc/region.c b/lib/isc/region.c
new file mode 100644
index 0000000..675f4ec
--- /dev/null
+++ b/lib/isc/region.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 <stdlib.h>
+#include <string.h>
+
+#include <isc/region.h>
+#include <isc/util.h>
+
+int
+isc_region_compare(isc_region_t *r1, isc_region_t *r2) {
+ unsigned int l;
+ int result;
+
+ REQUIRE(r1 != NULL);
+ REQUIRE(r2 != NULL);
+
+ l = (r1->length < r2->length) ? r1->length : r2->length;
+
+ if ((result = memcmp(r1->base, r2->base, l)) != 0) {
+ return ((result < 0) ? -1 : 1);
+ } else {
+ return ((r1->length == r2->length) ? 0
+ : (r1->length < r2->length) ? -1
+ : 1);
+ }
+}
diff --git a/lib/isc/resource.c b/lib/isc/resource.c
new file mode 100644
index 0000000..d83762c
--- /dev/null
+++ b/lib/isc/resource.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.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <sys/resource.h>
+#include <sys/time.h> /* Required on some systems for <sys/resource.h>. */
+#include <sys/types.h>
+
+#include <isc/resource.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#ifdef __linux__
+#include <linux/fs.h> /* To get the large NR_OPEN. */
+#endif /* ifdef __linux__ */
+
+#include "errno2result.h"
+
+static isc_result_t
+resource2rlim(isc_resource_t resource, int *rlim_resource) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ switch (resource) {
+ case isc_resource_coresize:
+ *rlim_resource = RLIMIT_CORE;
+ break;
+ case isc_resource_cputime:
+ *rlim_resource = RLIMIT_CPU;
+ break;
+ case isc_resource_datasize:
+ *rlim_resource = RLIMIT_DATA;
+ break;
+ case isc_resource_filesize:
+ *rlim_resource = RLIMIT_FSIZE;
+ break;
+ case isc_resource_lockedmemory:
+#ifdef RLIMIT_MEMLOCK
+ *rlim_resource = RLIMIT_MEMLOCK;
+#else /* ifdef RLIMIT_MEMLOCK */
+ result = ISC_R_NOTIMPLEMENTED;
+#endif /* ifdef RLIMIT_MEMLOCK */
+ break;
+ case isc_resource_openfiles:
+#ifdef RLIMIT_NOFILE
+ *rlim_resource = RLIMIT_NOFILE;
+#else /* ifdef RLIMIT_NOFILE */
+ result = ISC_R_NOTIMPLEMENTED;
+#endif /* ifdef RLIMIT_NOFILE */
+ break;
+ case isc_resource_processes:
+#ifdef RLIMIT_NPROC
+ *rlim_resource = RLIMIT_NPROC;
+#else /* ifdef RLIMIT_NPROC */
+ result = ISC_R_NOTIMPLEMENTED;
+#endif /* ifdef RLIMIT_NPROC */
+ break;
+ case isc_resource_residentsize:
+#ifdef RLIMIT_RSS
+ *rlim_resource = RLIMIT_RSS;
+#else /* ifdef RLIMIT_RSS */
+ result = ISC_R_NOTIMPLEMENTED;
+#endif /* ifdef RLIMIT_RSS */
+ break;
+ case isc_resource_stacksize:
+ *rlim_resource = RLIMIT_STACK;
+ break;
+ default:
+ /*
+ * This test is not very robust if isc_resource_t
+ * changes, but generates a clear assertion message.
+ */
+ REQUIRE(resource >= isc_resource_coresize &&
+ resource <= isc_resource_stacksize);
+
+ result = ISC_R_RANGE;
+ break;
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_resource_setlimit(isc_resource_t resource, isc_resourcevalue_t value) {
+ struct rlimit rl;
+ rlim_t rlim_value;
+ int unixresult;
+ int unixresource;
+ isc_result_t result;
+
+ result = resource2rlim(resource, &unixresource);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (value == ISC_RESOURCE_UNLIMITED) {
+ rlim_value = RLIM_INFINITY;
+ } else {
+ /*
+ * isc_resourcevalue_t was chosen as an unsigned 64 bit
+ * integer so that it could contain the maximum range of
+ * reasonable values. Unfortunately, this exceeds the typical
+ * range on Unix systems. Ensure the range of
+ * rlim_t is not overflowed.
+ */
+ isc_resourcevalue_t rlim_max;
+ bool rlim_t_is_signed = (((double)(rlim_t)-1) < 0);
+
+ if (rlim_t_is_signed) {
+ rlim_max = ~((rlim_t)1 << (sizeof(rlim_t) * 8 - 1));
+ } else {
+ rlim_max = (rlim_t)-1;
+ }
+
+ if (value > rlim_max) {
+ value = rlim_max;
+ }
+
+ rlim_value = value;
+ }
+
+ rl.rlim_cur = rl.rlim_max = rlim_value;
+ unixresult = setrlimit(unixresource, &rl);
+
+ if (unixresult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+#if defined(OPEN_MAX) && defined(__APPLE__)
+ /*
+ * The Darwin kernel doesn't accept RLIM_INFINITY for rlim_cur; the
+ * maximum possible value is OPEN_MAX. BIND8 used to use
+ * sysconf(_SC_OPEN_MAX) for such a case, but this value is much
+ * smaller than OPEN_MAX and is not really effective.
+ */
+ if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) {
+ rl.rlim_cur = OPEN_MAX;
+ unixresult = setrlimit(unixresource, &rl);
+ if (unixresult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+#elif defined(__linux__)
+#ifndef NR_OPEN
+#define NR_OPEN (1024 * 1024)
+#endif /* ifndef NR_OPEN */
+
+ /*
+ * Some Linux kernels don't accept RLIM_INFINIT; the maximum
+ * possible value is the NR_OPEN defined in linux/fs.h.
+ */
+ if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) {
+ rl.rlim_cur = rl.rlim_max = NR_OPEN;
+ unixresult = setrlimit(unixresource, &rl);
+ if (unixresult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+#endif /* if defined(OPEN_MAX) && defined(__APPLE__) */
+ if (resource == isc_resource_openfiles && rlim_value == RLIM_INFINITY) {
+ if (getrlimit(unixresource, &rl) == 0) {
+ rl.rlim_cur = rl.rlim_max;
+ unixresult = setrlimit(unixresource, &rl);
+ if (unixresult == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+ }
+ return (isc__errno2result(errno));
+}
+
+isc_result_t
+isc_resource_getlimit(isc_resource_t resource, isc_resourcevalue_t *value) {
+ int unixresource;
+ struct rlimit rl;
+ isc_result_t result;
+
+ result = resource2rlim(resource, &unixresource);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (getrlimit(unixresource, &rl) != 0) {
+ return (isc__errno2result(errno));
+ }
+
+ *value = rl.rlim_max;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_resource_getcurlimit(isc_resource_t resource, isc_resourcevalue_t *value) {
+ int unixresource;
+ struct rlimit rl;
+ isc_result_t result;
+
+ result = resource2rlim(resource, &unixresource);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (getrlimit(unixresource, &rl) != 0) {
+ return (isc__errno2result(errno));
+ }
+
+ *value = rl.rlim_cur;
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/result.c b/lib/isc/result.c
new file mode 100644
index 0000000..edbc8fd
--- /dev/null
+++ b/lib/isc/result.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+
+#include <isc/once.h>
+#include <isc/util.h>
+
+static const char *description[ISC_R_NRESULTS] = {
+ [ISC_R_SUCCESS] = "success",
+ [ISC_R_NOMEMORY] = "out of memory",
+ [ISC_R_TIMEDOUT] = "timed out",
+ [ISC_R_NOTHREADS] = "no available threads",
+ [ISC_R_ADDRNOTAVAIL] = "address not available",
+ [ISC_R_ADDRINUSE] = "address in use",
+ [ISC_R_NOPERM] = "permission denied",
+ [ISC_R_NOCONN] = "no pending connections",
+ [ISC_R_NETUNREACH] = "network unreachable",
+ [ISC_R_HOSTUNREACH] = "host unreachable",
+ [ISC_R_NETDOWN] = "network down",
+ [ISC_R_HOSTDOWN] = "host down",
+ [ISC_R_CONNREFUSED] = "connection refused",
+ [ISC_R_NORESOURCES] = "not enough free resources",
+ [ISC_R_EOF] = "end of file",
+ [ISC_R_BOUND] = "socket already bound",
+ [ISC_R_RELOAD] = "reload",
+ [ISC_R_LOCKBUSY] = "lock busy",
+ [ISC_R_EXISTS] = "already exists",
+ [ISC_R_NOSPACE] = "ran out of space",
+ [ISC_R_CANCELED] = "operation canceled",
+ [ISC_R_NOTBOUND] = "socket is not bound",
+ [ISC_R_SHUTTINGDOWN] = "shutting down",
+ [ISC_R_NOTFOUND] = "not found",
+ [ISC_R_UNEXPECTEDEND] = "unexpected end of input",
+ [ISC_R_FAILURE] = "failure",
+ [ISC_R_IOERROR] = "I/O error",
+ [ISC_R_NOTIMPLEMENTED] = "not implemented",
+ [ISC_R_UNBALANCED] = "unbalanced parentheses",
+ [ISC_R_NOMORE] = "no more",
+ [ISC_R_INVALIDFILE] = "invalid file",
+ [ISC_R_BADBASE64] = "bad base64 encoding",
+ [ISC_R_UNEXPECTEDTOKEN] = "unexpected token",
+ [ISC_R_QUOTA] = "quota reached",
+ [ISC_R_UNEXPECTED] = "unexpected error",
+ [ISC_R_ALREADYRUNNING] = "already running",
+ [ISC_R_IGNORE] = "ignore",
+ [ISC_R_MASKNONCONTIG] = "address mask not contiguous",
+ [ISC_R_FILENOTFOUND] = "file not found",
+ [ISC_R_FILEEXISTS] = "file already exists",
+ [ISC_R_NOTCONNECTED] = "socket is not connected",
+ [ISC_R_RANGE] = "out of range",
+ [ISC_R_NOENTROPY] = "out of entropy",
+ [ISC_R_MULTICAST] = "invalid use of multicast address",
+ [ISC_R_NOTFILE] = "not a file",
+ [ISC_R_NOTDIRECTORY] = "not a directory",
+ [ISC_R_EMPTY] = "queue is empty",
+ [ISC_R_FAMILYMISMATCH] = "address family mismatch",
+ [ISC_R_FAMILYNOSUPPORT] = "address family not supported",
+ [ISC_R_BADHEX] = "bad hex encoding",
+ [ISC_R_TOOMANYOPENFILES] = "too many open files",
+ [ISC_R_NOTBLOCKING] = "not blocking",
+ [ISC_R_UNBALANCEDQUOTES] = "unbalanced quotes",
+ [ISC_R_INPROGRESS] = "operation in progress",
+ [ISC_R_CONNECTIONRESET] = "connection reset",
+ [ISC_R_SOFTQUOTA] = "soft quota reached",
+ [ISC_R_BADNUMBER] = "not a valid number",
+ [ISC_R_DISABLED] = "disabled",
+ [ISC_R_MAXSIZE] = "max size",
+ [ISC_R_BADADDRESSFORM] = "invalid address format",
+ [ISC_R_BADBASE32] = "bad base32 encoding",
+ [ISC_R_UNSET] = "unset",
+ [ISC_R_MULTIPLE] = "multiple",
+ [ISC_R_WOULDBLOCK] = "would block",
+ [ISC_R_COMPLETE] = "complete",
+ [ISC_R_CRYPTOFAILURE] = "crypto failure",
+ [ISC_R_DISCQUOTA] = "disc quota",
+ [ISC_R_DISCFULL] = "disc full",
+ [ISC_R_DEFAULT] = "default",
+ [ISC_R_IPV4PREFIX] = "IPv4 prefix",
+ [ISC_R_TLSERROR] = "TLS error",
+ [ISC_R_TLSBADPEERCERT] = "TLS peer certificate verification failed",
+ [ISC_R_HTTP2ALPNERROR] = "ALPN for HTTP/2 failed",
+ [ISC_R_DOTALPNERROR] = "ALPN for DoT failed",
+ [ISC_R_INVALIDPROTO] = "invalid protocol",
+
+ [DNS_R_LABELTOOLONG] = "label too long",
+ [DNS_R_BADESCAPE] = "bad escape",
+ [DNS_R_EMPTYLABEL] = "empty label",
+ [DNS_R_BADDOTTEDQUAD] = "bad dotted quad",
+ [DNS_R_INVALIDNS] = "invalid NS owner name (wildcard)",
+ [DNS_R_UNKNOWN] = "unknown class/type",
+ [DNS_R_BADLABELTYPE] = "bad label type",
+ [DNS_R_BADPOINTER] = "bad compression pointer",
+ [DNS_R_TOOMANYHOPS] = "too many hops",
+ [DNS_R_DISALLOWED] = "disallowed (by application policy)",
+ [DNS_R_EXTRATOKEN] = "extra input text",
+ [DNS_R_EXTRADATA] = "extra input data",
+ [DNS_R_TEXTTOOLONG] = "text too long",
+ [DNS_R_NOTZONETOP] = "not at top of zone",
+ [DNS_R_SYNTAX] = "syntax error",
+ [DNS_R_BADCKSUM] = "bad checksum",
+ [DNS_R_BADAAAA] = "bad IPv6 address",
+ [DNS_R_NOOWNER] = "no owner",
+ [DNS_R_NOTTL] = "no ttl",
+ [DNS_R_BADCLASS] = "bad class",
+ [DNS_R_NAMETOOLONG] = "name too long",
+ [DNS_R_PARTIALMATCH] = "partial match",
+ [DNS_R_NEWORIGIN] = "new origin",
+ [DNS_R_UNCHANGED] = "unchanged",
+ [DNS_R_BADTTL] = "bad ttl",
+ [DNS_R_NOREDATA] = "more data needed/to be rendered",
+ [DNS_R_CONTINUE] = "continue",
+ [DNS_R_DELEGATION] = "delegation",
+ [DNS_R_GLUE] = "glue",
+ [DNS_R_DNAME] = "dname",
+ [DNS_R_CNAME] = "cname",
+ [DNS_R_BADDB] = "bad database",
+ [DNS_R_ZONECUT] = "zonecut",
+ [DNS_R_BADZONE] = "bad zone",
+ [DNS_R_MOREDATA] = "more data",
+ [DNS_R_UPTODATE] = "up to date",
+ [DNS_R_TSIGVERIFYFAILURE] = "tsig verify failure",
+ [DNS_R_TSIGERRORSET] = "tsig indicates error",
+ [DNS_R_SIGINVALID] = "RRSIG failed to verify",
+ [DNS_R_SIGEXPIRED] = "RRSIG has expired",
+ [DNS_R_SIGFUTURE] = "RRSIG validity period has not begun",
+ [DNS_R_KEYUNAUTHORIZED] = "key is unauthorized to sign data",
+ [DNS_R_INVALIDTIME] = "invalid time",
+ [DNS_R_EXPECTEDTSIG] = "expected a TSIG or SIG(0)",
+ [DNS_R_UNEXPECTEDTSIG] = "did not expect a TSIG or SIG(0)",
+ [DNS_R_INVALIDTKEY] = "TKEY is unacceptable",
+ [DNS_R_HINT] = "hint",
+ [DNS_R_DROP] = "drop",
+ [DNS_R_NOTLOADED] = "zone not loaded",
+ [DNS_R_NCACHENXDOMAIN] = "ncache nxdomain",
+ [DNS_R_NCACHENXRRSET] = "ncache nxrrset",
+ [DNS_R_WAIT] = "wait",
+ [DNS_R_NOTVERIFIEDYET] = "not verified yet",
+ [DNS_R_NOIDENTITY] = "no identity",
+ [DNS_R_NOJOURNAL] = "no journal",
+ [DNS_R_ALIAS] = "alias",
+ [DNS_R_USETCP] = "use TCP",
+ [DNS_R_NOVALIDSIG] = "no valid RRSIG",
+ [DNS_R_NOVALIDNSEC] = "no valid NSEC",
+ [DNS_R_NOTINSECURE] = "insecurity proof failed",
+ [DNS_R_UNKNOWNSERVICE] = "unknown service",
+ [DNS_R_RECOVERABLE] = "recoverable error occurred",
+ [DNS_R_UNKNOWNOPT] = "unknown opt attribute record",
+ [DNS_R_UNEXPECTEDID] = "unexpected message id",
+ [DNS_R_SEENINCLUDE] = "seen include file",
+ [DNS_R_NOTEXACT] = "not exact",
+ [DNS_R_BLACKHOLED] = "address blackholed",
+ [DNS_R_BADALG] = "bad algorithm",
+ [DNS_R_METATYPE] = "invalid use of a meta type",
+ [DNS_R_CNAMEANDOTHER] = "CNAME and other data",
+ [DNS_R_SINGLETON] = "multiple RRs of singleton type",
+ [DNS_R_HINTNXRRSET] = "hint nxrrset",
+ [DNS_R_NOMASTERFILE] = "no master file configured",
+ [DNS_R_UNKNOWNPROTO] = "unknown protocol",
+ [DNS_R_CLOCKSKEW] = "clocks are unsynchronized",
+ [DNS_R_BADIXFR] = "IXFR failed",
+ [DNS_R_NOTAUTHORITATIVE] = "not authoritative",
+ [DNS_R_NOVALIDKEY] = "no valid KEY",
+ [DNS_R_OBSOLETE] = "obsolete",
+ [DNS_R_FROZEN] = "already frozen",
+ [DNS_R_UNKNOWNFLAG] = "unknown flag",
+ [DNS_R_EXPECTEDRESPONSE] = "expected a response",
+ [DNS_R_NOVALIDDS] = "no valid DS",
+ [DNS_R_NSISADDRESS] = "NS is an address",
+ [DNS_R_REMOTEFORMERR] = "received FORMERR",
+ [DNS_R_TRUNCATEDTCP] = "truncated TCP response",
+ [DNS_R_LAME] = "lame server detected",
+ [DNS_R_UNEXPECTEDRCODE] = "unexpected RCODE",
+ [DNS_R_UNEXPECTEDOPCODE] = "unexpected OPCODE",
+ [DNS_R_CHASEDSSERVERS] = "chase DS servers",
+ [DNS_R_EMPTYNAME] = "empty name",
+ [DNS_R_EMPTYWILD] = "empty wild",
+ [DNS_R_BADBITMAP] = "bad bitmap",
+ [DNS_R_FROMWILDCARD] = "from wildcard",
+ [DNS_R_BADOWNERNAME] = "bad owner name (check-names)",
+ [DNS_R_BADNAME] = "bad name (check-names)",
+ [DNS_R_DYNAMIC] = "dynamic zone",
+ [DNS_R_UNKNOWNCOMMAND] = "unknown command",
+ [DNS_R_MUSTBESECURE] = "must-be-secure",
+ [DNS_R_COVERINGNSEC] = "covering NSEC record returned",
+ [DNS_R_MXISADDRESS] = "MX is an address",
+ [DNS_R_DUPLICATE] = "duplicate query",
+ [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)",
+ [DNS_R_NOTPRIMARY] = "not primary",
+ [DNS_R_BROKENCHAIN] = "broken trust chain",
+ [DNS_R_EXPIRED] = "expired",
+ [DNS_R_NOTDYNAMIC] = "not dynamic",
+ [DNS_R_BADEUI] = "bad EUI",
+ [DNS_R_NTACOVERED] = "covered by negative trust anchor",
+ [DNS_R_BADCDS] = "bad CDS",
+ [DNS_R_BADCDNSKEY] = "bad CDNSKEY",
+ [DNS_R_OPTERR] = "malformed OPT option",
+ [DNS_R_BADDNSTAP] = "malformed DNSTAP data",
+ [DNS_R_BADTSIG] = "TSIG in wrong location",
+ [DNS_R_BADSIG0] = "SIG(0) in wrong location",
+ [DNS_R_TOOMANYRECORDS] = "too many records",
+ [DNS_R_VERIFYFAILURE] = "verify failure",
+ [DNS_R_ATZONETOP] = "at top of zone",
+ [DNS_R_NOKEYMATCH] = "no matching key found",
+ [DNS_R_TOOMANYKEYS] = "too many keys matching",
+ [DNS_R_KEYNOTACTIVE] = "key is not actively signing",
+ [DNS_R_NSEC3ITERRANGE] = "NSEC3 iterations out of range",
+ [DNS_R_NSEC3SALTRANGE] = "NSEC3 salt length too high",
+ [DNS_R_NSEC3BADALG] = "cannot use NSEC3 with key algorithm",
+ [DNS_R_NSEC3RESALT] = "NSEC3 resalt",
+ [DNS_R_INCONSISTENTRR] = "inconsistent resource record",
+ [DNS_R_NOALPN] = "no ALPN",
+
+ [DST_R_UNSUPPORTEDALG] = "algorithm is unsupported",
+ [DST_R_CRYPTOFAILURE] = "crypto failure",
+ [DST_R_NOCRYPTO] = "built with no crypto support",
+ [DST_R_NULLKEY] = "illegal operation for a null key",
+ [DST_R_INVALIDPUBLICKEY] = "public key is invalid",
+ [DST_R_INVALIDPRIVATEKEY] = "private key is invalid",
+ [DST_R_WRITEERROR] = "error occurred writing key to disk",
+ [DST_R_INVALIDPARAM] = "invalid algorithm specific parameter",
+ [DST_R_SIGNFAILURE] = "sign failure",
+ [DST_R_VERIFYFAILURE] = "verify failure",
+ [DST_R_NOTPUBLICKEY] = "not a public key",
+ [DST_R_NOTPRIVATEKEY] = "not a private key",
+ [DST_R_KEYCANNOTCOMPUTESECRET] = "not a key that can compute a secret",
+ [DST_R_COMPUTESECRETFAILURE] = "failure computing a shared secret",
+ [DST_R_NORANDOMNESS] = "no randomness available",
+ [DST_R_BADKEYTYPE] = "bad key type",
+ [DST_R_NOENGINE] = "no engine",
+ [DST_R_EXTERNALKEY] = "illegal operation for an external key",
+
+ [DNS_R_NOERROR] = "NOERROR",
+ [DNS_R_FORMERR] = "FORMERR",
+ [DNS_R_SERVFAIL] = "SERVFAIL",
+ [DNS_R_NXDOMAIN] = "NXDOMAIN",
+ [DNS_R_NOTIMP] = "NOTIMP",
+ [DNS_R_REFUSED] = "REFUSED",
+ [DNS_R_YXDOMAIN] = "YXDOMAIN",
+ [DNS_R_YXRRSET] = "YXRRSET",
+ [DNS_R_NXRRSET] = "NXRRSET",
+ [DNS_R_NOTAUTH] = "NOTAUTH",
+ [DNS_R_NOTZONE] = "NOTZONE",
+ [DNS_R_RCODE11] = "<rcode 11>",
+ [DNS_R_RCODE12] = "<rcode 12>",
+ [DNS_R_RCODE13] = "<rcode 13>",
+ [DNS_R_RCODE14] = "<rcode 14>",
+ [DNS_R_RCODE15] = "<rcode 15>",
+ [DNS_R_BADVERS] = "BADVERS",
+ [DNS_R_BADCOOKIE] = "BADCOOKIE",
+
+ [ISCCC_R_UNKNOWNVERSION] = "unknown version",
+ [ISCCC_R_SYNTAX] = "syntax error",
+ [ISCCC_R_BADAUTH] = "bad auth",
+ [ISCCC_R_EXPIRED] = "expired",
+ [ISCCC_R_CLOCKSKEW] = "clock skew",
+ [ISCCC_R_DUPLICATE] = "duplicate",
+ [ISCCC_R_MAXDEPTH] = "max depth",
+};
+
+static const char *identifier[ISC_R_NRESULTS] = {
+ [ISC_R_SUCCESS] = "ISC_R_SUCCESS",
+ [ISC_R_NOMEMORY] = "ISC_R_NOMEMORY",
+ [ISC_R_TIMEDOUT] = "ISC_R_TIMEDOUT",
+ [ISC_R_NOTHREADS] = "ISC_R_NOTHREADS",
+ [ISC_R_ADDRNOTAVAIL] = "ISC_R_ADDRNOTAVAIL",
+ [ISC_R_ADDRINUSE] = "ISC_R_ADDRINUSE",
+ [ISC_R_NOPERM] = "ISC_R_NOPERM",
+ [ISC_R_NOCONN] = "ISC_R_NOCONN",
+ [ISC_R_NETUNREACH] = "ISC_R_NETUNREACH",
+ [ISC_R_HOSTUNREACH] = "ISC_R_HOSTUNREACH",
+ [ISC_R_NETDOWN] = "ISC_R_NETDOWN",
+ [ISC_R_HOSTDOWN] = "ISC_R_HOSTDOWN",
+ [ISC_R_CONNREFUSED] = "ISC_R_CONNREFUSED",
+ [ISC_R_NORESOURCES] = "ISC_R_NORESOURCES",
+ [ISC_R_EOF] = "ISC_R_EOF",
+ [ISC_R_BOUND] = "ISC_R_BOUND",
+ [ISC_R_RELOAD] = "ISC_R_RELOAD",
+ [ISC_R_LOCKBUSY] = "ISC_R_LOCKBUSY",
+ [ISC_R_EXISTS] = "ISC_R_EXISTS",
+ [ISC_R_NOSPACE] = "ISC_R_NOSPACE",
+ [ISC_R_CANCELED] = "ISC_R_CANCELED",
+ [ISC_R_NOTBOUND] = "ISC_R_NOTBOUND",
+ [ISC_R_SHUTTINGDOWN] = "ISC_R_SHUTTINGDOWN",
+ [ISC_R_NOTFOUND] = "ISC_R_NOTFOUND",
+ [ISC_R_UNEXPECTEDEND] = "ISC_R_UNEXPECTEDEND",
+ [ISC_R_FAILURE] = "ISC_R_FAILURE",
+ [ISC_R_IOERROR] = "ISC_R_IOERROR",
+ [ISC_R_NOTIMPLEMENTED] = "ISC_R_NOTIMPLEMENTED",
+ [ISC_R_UNBALANCED] = "ISC_R_UNBALANCED",
+ [ISC_R_NOMORE] = "ISC_R_NOMORE",
+ [ISC_R_INVALIDFILE] = "ISC_R_INVALIDFILE",
+ [ISC_R_BADBASE64] = "ISC_R_BADBASE64",
+ [ISC_R_UNEXPECTEDTOKEN] = "ISC_R_UNEXPECTEDTOKEN",
+ [ISC_R_QUOTA] = "ISC_R_QUOTA",
+ [ISC_R_UNEXPECTED] = "ISC_R_UNEXPECTED",
+ [ISC_R_ALREADYRUNNING] = "ISC_R_ALREADYRUNNING",
+ [ISC_R_IGNORE] = "ISC_R_IGNORE",
+ [ISC_R_MASKNONCONTIG] = "ISC_R_MASKNONCONTIG",
+ [ISC_R_FILENOTFOUND] = "ISC_R_FILENOTFOUND",
+ [ISC_R_FILEEXISTS] = "ISC_R_FILEEXISTS",
+ [ISC_R_NOTCONNECTED] = "ISC_R_NOTCONNECTED",
+ [ISC_R_RANGE] = "ISC_R_RANGE",
+ [ISC_R_NOENTROPY] = "ISC_R_NOENTROPY",
+ [ISC_R_MULTICAST] = "ISC_R_MULTICAST",
+ [ISC_R_NOTFILE] = "ISC_R_NOTFILE",
+ [ISC_R_NOTDIRECTORY] = "ISC_R_NOTDIRECTORY",
+ [ISC_R_EMPTY] = "ISC_R_EMPTY",
+ [ISC_R_FAMILYMISMATCH] = "ISC_R_FAMILYMISMATCH",
+ [ISC_R_FAMILYNOSUPPORT] = "ISC_R_FAMILYNOSUPPORT",
+ [ISC_R_BADHEX] = "ISC_R_BADHEX",
+ [ISC_R_TOOMANYOPENFILES] = "ISC_R_TOOMANYOPENFILES",
+ [ISC_R_NOTBLOCKING] = "ISC_R_NOTBLOCKING",
+ [ISC_R_UNBALANCEDQUOTES] = "ISC_R_UNBALANCEDQUOTES",
+ [ISC_R_INPROGRESS] = "ISC_R_INPROGRESS",
+ [ISC_R_CONNECTIONRESET] = "ISC_R_CONNECTIONRESET",
+ [ISC_R_SOFTQUOTA] = "ISC_R_SOFTQUOTA",
+ [ISC_R_BADNUMBER] = "ISC_R_BADNUMBER",
+ [ISC_R_DISABLED] = "ISC_R_DISABLED",
+ [ISC_R_MAXSIZE] = "ISC_R_MAXSIZE",
+ [ISC_R_BADADDRESSFORM] = "ISC_R_BADADDRESSFORM",
+ [ISC_R_BADBASE32] = "ISC_R_BADBASE32",
+ [ISC_R_UNSET] = "ISC_R_UNSET",
+ [ISC_R_MULTIPLE] = "ISC_R_MULTIPLE",
+ [ISC_R_WOULDBLOCK] = "ISC_R_WOULDBLOCK",
+ [ISC_R_COMPLETE] = "ISC_R_COMPLETE",
+ [ISC_R_CRYPTOFAILURE] = "ISC_R_CRYPTOFAILURE",
+ [ISC_R_DISCQUOTA] = "ISC_R_DISCQUOTA",
+ [ISC_R_DISCFULL] = "ISC_R_DISCFULL",
+ [ISC_R_DEFAULT] = "ISC_R_DEFAULT",
+ [ISC_R_IPV4PREFIX] = "ISC_R_IPV4PREFIX",
+ [ISC_R_TLSERROR] = "ISC_R_TLSERROR",
+ [ISC_R_TLSBADPEERCERT] = "ISC_R_TLSBADPEERCERT",
+ [ISC_R_HTTP2ALPNERROR] = "ISC_R_HTTP2ALPNERROR",
+ [ISC_R_DOTALPNERROR] = "ISC_R_DOTALPNERROR",
+ [DNS_R_LABELTOOLONG] = "DNS_R_LABELTOOLONG",
+ [DNS_R_BADESCAPE] = "DNS_R_BADESCAPE",
+ [DNS_R_EMPTYLABEL] = "DNS_R_EMPTYLABEL",
+ [DNS_R_BADDOTTEDQUAD] = "DNS_R_BADDOTTEDQUAD",
+ [DNS_R_INVALIDNS] = "DNS_R_INVALIDNS",
+ [DNS_R_UNKNOWN] = "DNS_R_UNKNOWN",
+ [DNS_R_BADLABELTYPE] = "DNS_R_BADLABELTYPE",
+ [DNS_R_BADPOINTER] = "DNS_R_BADPOINTER",
+ [DNS_R_TOOMANYHOPS] = "DNS_R_TOOMANYHOPS",
+ [DNS_R_DISALLOWED] = "DNS_R_DISALLOWED",
+ [DNS_R_EXTRATOKEN] = "DNS_R_EXTRATOKEN",
+ [DNS_R_EXTRADATA] = "DNS_R_EXTRADATA",
+ [DNS_R_TEXTTOOLONG] = "DNS_R_TEXTTOOLONG",
+ [DNS_R_NOTZONETOP] = "DNS_R_NOTZONETOP",
+ [DNS_R_SYNTAX] = "DNS_R_SYNTAX",
+ [DNS_R_BADCKSUM] = "DNS_R_BADCKSUM",
+ [DNS_R_BADAAAA] = "DNS_R_BADAAAA",
+ [DNS_R_NOOWNER] = "DNS_R_NOOWNER",
+ [DNS_R_NOTTL] = "DNS_R_NOTTL",
+ [DNS_R_BADCLASS] = "DNS_R_BADCLASS",
+ [DNS_R_NAMETOOLONG] = "DNS_R_NAMETOOLONG",
+ [DNS_R_PARTIALMATCH] = "DNS_R_PARTIALMATCH",
+ [DNS_R_NEWORIGIN] = "DNS_R_NEWORIGIN",
+ [DNS_R_UNCHANGED] = "DNS_R_UNCHANGED",
+ [DNS_R_BADTTL] = "DNS_R_BADTTL",
+ [DNS_R_NOREDATA] = "DNS_R_NOREDATA",
+ [DNS_R_CONTINUE] = "DNS_R_CONTINUE",
+ [DNS_R_DELEGATION] = "DNS_R_DELEGATION",
+ [DNS_R_GLUE] = "DNS_R_GLUE",
+ [DNS_R_DNAME] = "DNS_R_DNAME",
+ [DNS_R_CNAME] = "DNS_R_CNAME",
+ [DNS_R_BADDB] = "DNS_R_BADDB",
+ [DNS_R_ZONECUT] = "DNS_R_ZONECUT",
+ [DNS_R_BADZONE] = "DNS_R_BADZONE",
+ [DNS_R_MOREDATA] = "DNS_R_MOREDATA",
+ [DNS_R_UPTODATE] = "DNS_R_UPTODATE",
+ [DNS_R_TSIGVERIFYFAILURE] = "DNS_R_TSIGVERIFYFAILURE",
+ [DNS_R_TSIGERRORSET] = "DNS_R_TSIGERRORSET",
+ [DNS_R_SIGINVALID] = "DNS_R_SIGINVALID",
+ [DNS_R_SIGEXPIRED] = "DNS_R_SIGEXPIRED",
+ [DNS_R_SIGFUTURE] = "DNS_R_SIGFUTURE",
+ [DNS_R_KEYUNAUTHORIZED] = "DNS_R_KEYUNAUTHORIZED",
+ [DNS_R_INVALIDTIME] = "DNS_R_INVALIDTIME",
+ [DNS_R_EXPECTEDTSIG] = "DNS_R_EXPECTEDTSIG",
+ [DNS_R_UNEXPECTEDTSIG] = "DNS_R_UNEXPECTEDTSIG",
+ [DNS_R_INVALIDTKEY] = "DNS_R_INVALIDTKEY",
+ [DNS_R_HINT] = "DNS_R_HINT",
+ [DNS_R_DROP] = "DNS_R_DROP",
+ [DNS_R_NOTLOADED] = "DNS_R_NOTLOADED",
+ [DNS_R_NCACHENXDOMAIN] = "DNS_R_NCACHENXDOMAIN",
+ [DNS_R_NCACHENXRRSET] = "DNS_R_NCACHENXRRSET",
+ [DNS_R_WAIT] = "DNS_R_WAIT",
+ [DNS_R_NOTVERIFIEDYET] = "DNS_R_NOTVERIFIEDYET",
+ [DNS_R_NOIDENTITY] = "DNS_R_NOIDENTITY",
+ [DNS_R_NOJOURNAL] = "DNS_R_NOJOURNAL",
+ [DNS_R_ALIAS] = "DNS_R_ALIAS",
+ [DNS_R_USETCP] = "DNS_R_USETCP",
+ [DNS_R_NOVALIDSIG] = "DNS_R_NOVALIDSIG",
+ [DNS_R_NOVALIDNSEC] = "DNS_R_NOVALIDNSEC",
+ [DNS_R_NOTINSECURE] = "DNS_R_NOTINSECURE",
+ [DNS_R_UNKNOWNSERVICE] = "DNS_R_UNKNOWNSERVICE",
+ [DNS_R_RECOVERABLE] = "DNS_R_RECOVERABLE",
+ [DNS_R_UNKNOWNOPT] = "DNS_R_UNKNOWNOPT",
+ [DNS_R_UNEXPECTEDID] = "DNS_R_UNEXPECTEDID",
+ [DNS_R_SEENINCLUDE] = "DNS_R_SEENINCLUDE",
+ [DNS_R_NOTEXACT] = "DNS_R_NOTEXACT",
+ [DNS_R_BLACKHOLED] = "DNS_R_BLACKHOLED",
+ [DNS_R_BADALG] = "DNS_R_BADALG",
+ [DNS_R_METATYPE] = "DNS_R_METATYPE",
+ [DNS_R_CNAMEANDOTHER] = "DNS_R_CNAMEANDOTHER",
+ [DNS_R_SINGLETON] = "DNS_R_SINGLETON",
+ [DNS_R_HINTNXRRSET] = "DNS_R_HINTNXRRSET",
+ [DNS_R_NOMASTERFILE] = "DNS_R_NOMASTERFILE",
+ [DNS_R_UNKNOWNPROTO] = "DNS_R_UNKNOWNPROTO",
+ [DNS_R_CLOCKSKEW] = "DNS_R_CLOCKSKEW",
+ [DNS_R_BADIXFR] = "DNS_R_BADIXFR",
+ [DNS_R_NOTAUTHORITATIVE] = "DNS_R_NOTAUTHORITATIVE",
+ [DNS_R_NOVALIDKEY] = "DNS_R_NOVALIDKEY",
+ [DNS_R_OBSOLETE] = "DNS_R_OBSOLETE",
+ [DNS_R_FROZEN] = "DNS_R_FROZEN",
+ [DNS_R_UNKNOWNFLAG] = "DNS_R_UNKNOWNFLAG",
+ [DNS_R_EXPECTEDRESPONSE] = "DNS_R_EXPECTEDRESPONSE",
+ [DNS_R_NOVALIDDS] = "DNS_R_NOVALIDDS",
+ [DNS_R_NSISADDRESS] = "DNS_R_NSISADDRESS",
+ [DNS_R_REMOTEFORMERR] = "DNS_R_REMOTEFORMERR",
+ [DNS_R_TRUNCATEDTCP] = "DNS_R_TRUNCATEDTCP",
+ [DNS_R_LAME] = "DNS_R_LAME",
+ [DNS_R_UNEXPECTEDRCODE] = "DNS_R_UNEXPECTEDRCODE",
+ [DNS_R_UNEXPECTEDOPCODE] = "DNS_R_UNEXPECTEDOPCODE",
+ [DNS_R_CHASEDSSERVERS] = "DNS_R_CHASEDSSERVERS",
+ [DNS_R_EMPTYNAME] = "DNS_R_EMPTYNAME",
+ [DNS_R_EMPTYWILD] = "DNS_R_EMPTYWILD",
+ [DNS_R_BADBITMAP] = "DNS_R_BADBITMAP",
+ [DNS_R_FROMWILDCARD] = "DNS_R_FROMWILDCARD",
+ [DNS_R_BADOWNERNAME] = "DNS_R_BADOWNERNAME",
+ [DNS_R_BADNAME] = "DNS_R_BADNAME",
+ [DNS_R_DYNAMIC] = "DNS_R_DYNAMIC",
+ [DNS_R_UNKNOWNCOMMAND] = "DNS_R_UNKNOWNCOMMAND",
+ [DNS_R_MUSTBESECURE] = "DNS_R_MUSTBESECURE",
+ [DNS_R_COVERINGNSEC] = "DNS_R_COVERINGNSEC",
+ [DNS_R_MXISADDRESS] = "DNS_R_MXISADDRESS",
+ [DNS_R_DUPLICATE] = "DNS_R_DUPLICATE",
+ [DNS_R_INVALIDNSEC3] = "DNS_R_INVALIDNSEC3",
+ [DNS_R_NOTPRIMARY] = "DNS_R_NOTPRIMARY",
+ [DNS_R_BROKENCHAIN] = "DNS_R_BROKENCHAIN",
+ [DNS_R_EXPIRED] = "DNS_R_EXPIRED",
+ [DNS_R_NOTDYNAMIC] = "DNS_R_NOTDYNAMIC",
+ [DNS_R_BADEUI] = "DNS_R_BADEUI",
+ [DNS_R_NTACOVERED] = "DNS_R_NTACOVERED",
+ [DNS_R_BADCDS] = "DNS_R_BADCDS",
+ [DNS_R_BADCDNSKEY] = "DNS_R_BADCDNSKEY",
+ [DNS_R_OPTERR] = "DNS_R_OPTERR",
+ [DNS_R_BADDNSTAP] = "DNS_R_BADDNSTAP",
+ [DNS_R_BADTSIG] = "DNS_R_BADTSIG",
+ [DNS_R_BADSIG0] = "DNS_R_BADSIG0",
+ [DNS_R_TOOMANYRECORDS] = "DNS_R_TOOMANYRECORDS",
+ [DNS_R_VERIFYFAILURE] = "DNS_R_VERIFYFAILURE",
+ [DNS_R_ATZONETOP] = "DNS_R_ATZONETOP",
+ [DNS_R_NOKEYMATCH] = "DNS_R_NOKEYMATCH",
+ [DNS_R_TOOMANYKEYS] = "DNS_R_TOOMANYKEYS",
+ [DNS_R_KEYNOTACTIVE] = "DNS_R_KEYNOTACTIVE",
+ [DNS_R_NSEC3ITERRANGE] = "DNS_R_NSEC3ITERRANGE",
+ [DNS_R_NSEC3SALTRANGE] = "DNS_R_NSEC3SALTRANGE",
+ [DNS_R_NSEC3BADALG] = "DNS_R_NSEC3BADALG",
+ [DNS_R_NSEC3RESALT] = "DNS_R_NSEC3RESALT",
+ [DNS_R_INCONSISTENTRR] = "DNS_R_INCONSISTENTRR",
+ [DNS_R_NOALPN] = "DNS_R_NOALPN",
+
+ [DST_R_UNSUPPORTEDALG] = "DST_R_UNSUPPORTEDALG",
+ [DST_R_CRYPTOFAILURE] = "DST_R_CRYPTOFAILURE",
+ [DST_R_NOCRYPTO] = "DST_R_NOCRYPTO",
+ [DST_R_NULLKEY] = "DST_R_NULLKEY",
+ [DST_R_INVALIDPUBLICKEY] = "DST_R_INVALIDPUBLICKEY",
+ [DST_R_INVALIDPRIVATEKEY] = "DST_R_INVALIDPRIVATEKEY",
+ [DST_R_WRITEERROR] = "DST_R_WRITEERROR",
+ [DST_R_INVALIDPARAM] = "DST_R_INVALIDPARAM",
+ [DST_R_SIGNFAILURE] = "DST_R_SIGNFAILURE",
+ [DST_R_VERIFYFAILURE] = "DST_R_VERIFYFAILURE",
+ [DST_R_NOTPUBLICKEY] = "DST_R_NOTPUBLICKEY",
+ [DST_R_NOTPRIVATEKEY] = "DST_R_NOTPRIVATEKEY",
+ [DST_R_KEYCANNOTCOMPUTESECRET] = "DST_R_KEYCANNOTCOMPUTESECRET",
+ [DST_R_COMPUTESECRETFAILURE] = "DST_R_COMPUTESECRETFAILURE",
+ [DST_R_NORANDOMNESS] = "DST_R_NORANDOMNESS",
+ [DST_R_BADKEYTYPE] = "DST_R_BADKEYTYPE",
+ [DST_R_NOENGINE] = "DST_R_NOENGINE",
+ [DST_R_EXTERNALKEY] = "DST_R_EXTERNALKEY",
+
+ [DNS_R_NOERROR] = "DNS_R_NOERROR",
+ [DNS_R_FORMERR] = "DNS_R_FORMERR",
+ [DNS_R_SERVFAIL] = "DNS_R_SERVFAIL",
+ [DNS_R_NXDOMAIN] = "DNS_R_NXDOMAIN",
+ [DNS_R_NOTIMP] = "DNS_R_NOTIMP",
+ [DNS_R_REFUSED] = "DNS_R_REFUSED",
+ [DNS_R_YXDOMAIN] = "DNS_R_YXDOMAIN",
+ [DNS_R_YXRRSET] = "DNS_R_YXRRSET",
+ [DNS_R_NXRRSET] = "DNS_R_NXRRSET",
+ [DNS_R_NOTAUTH] = "DNS_R_NOTAUTH",
+ [DNS_R_NOTZONE] = "DNS_R_NOTZONE",
+ [DNS_R_RCODE11] = "DNS_R_RCODE11",
+ [DNS_R_RCODE12] = "RNS_R_RCODE12",
+ [DNS_R_RCODE13] = "DNS_R_RCODE13",
+ [DNS_R_RCODE14] = "DNS_R_RCODE14",
+ [DNS_R_RCODE15] = "DNS_R_RCODE15",
+ [DNS_R_BADVERS] = "DNS_R_BADVERS",
+ [DNS_R_BADCOOKIE] = "DNS_R_BADCOOKIE",
+
+ [ISCCC_R_UNKNOWNVERSION] = "ISCCC_R_UNKNOWNVERSION",
+ [ISCCC_R_SYNTAX] = "ISCCC_R_SYNTAX",
+ [ISCCC_R_BADAUTH] = "ISCCC_R_BADAUTH",
+ [ISCCC_R_EXPIRED] = "ISCCC_R_EXPIRED",
+ [ISCCC_R_CLOCKSKEW] = "ISCCC_R_CLOCKSKEW",
+ [ISCCC_R_DUPLICATE] = "ISCCC_R_DUPLICATE",
+ [ISCCC_R_MAXDEPTH] = "ISCCC_R_MAXDEPTH",
+};
+
+STATIC_ASSERT((DNS_R_SERVFAIL - DNS_R_NOERROR == 2),
+ "DNS_R_NOERROR has wrong value");
+
+STATIC_ASSERT((DNS_R_BADVERS - DNS_R_NOERROR == 16),
+ "DNS_R_BADVERS has wrong value");
+
+STATIC_ASSERT((ISC_R_NRESULTS < INT32_MAX), "result.h enum too big");
+
+const char *
+isc_result_totext(isc_result_t result) {
+ return (description[result]);
+}
+
+const char *
+isc_result_toid(isc_result_t result) {
+ return (identifier[result]);
+}
diff --git a/lib/isc/rwlock.c b/lib/isc/rwlock.c
new file mode 100644
index 0000000..156b469
--- /dev/null
+++ b/lib/isc/rwlock.c
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stddef.h>
+
+#if defined(sun) && (defined(__sparc) || defined(__sparc__))
+#include <synch.h> /* for smt_pause(3c) */
+#endif /* if defined(sun) && (defined(__sparc) || defined(__sparc__)) */
+
+#include <isc/atomic.h>
+#include <isc/magic.h>
+#include <isc/print.h>
+#include <isc/rwlock.h>
+#include <isc/util.h>
+
+#if USE_PTHREAD_RWLOCK
+
+#include <errno.h>
+#include <pthread.h>
+
+void
+isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota,
+ unsigned int write_quota) {
+ UNUSED(read_quota);
+ UNUSED(write_quota);
+ REQUIRE(pthread_rwlock_init(&rwl->rwlock, NULL) == 0);
+ atomic_init(&rwl->downgrade, false);
+}
+
+isc_result_t
+isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ switch (type) {
+ case isc_rwlocktype_read:
+ REQUIRE(pthread_rwlock_rdlock(&rwl->rwlock) == 0);
+ break;
+ case isc_rwlocktype_write:
+ while (true) {
+ REQUIRE(pthread_rwlock_wrlock(&rwl->rwlock) == 0);
+ /* Unlock if in middle of downgrade operation */
+ if (atomic_load_acquire(&rwl->downgrade)) {
+ REQUIRE(pthread_rwlock_unlock(&rwl->rwlock) ==
+ 0);
+ while (atomic_load_acquire(&rwl->downgrade)) {
+ }
+ continue;
+ }
+ break;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ int ret = 0;
+ switch (type) {
+ case isc_rwlocktype_read:
+ ret = pthread_rwlock_tryrdlock(&rwl->rwlock);
+ break;
+ case isc_rwlocktype_write:
+ ret = pthread_rwlock_trywrlock(&rwl->rwlock);
+ if ((ret == 0) && atomic_load_acquire(&rwl->downgrade)) {
+ isc_rwlock_unlock(rwl, type);
+ return (ISC_R_LOCKBUSY);
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ switch (ret) {
+ case 0:
+ return (ISC_R_SUCCESS);
+ case EBUSY:
+ return (ISC_R_LOCKBUSY);
+ case EAGAIN:
+ return (ISC_R_LOCKBUSY);
+ default:
+ UNREACHABLE();
+ }
+}
+
+isc_result_t
+isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ UNUSED(type);
+ REQUIRE(pthread_rwlock_unlock(&rwl->rwlock) == 0);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_rwlock_tryupgrade(isc_rwlock_t *rwl) {
+ UNUSED(rwl);
+ return (ISC_R_LOCKBUSY);
+}
+
+void
+isc_rwlock_downgrade(isc_rwlock_t *rwl) {
+ isc_result_t result;
+ atomic_store_release(&rwl->downgrade, true);
+ result = isc_rwlock_unlock(rwl, isc_rwlocktype_write);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = isc_rwlock_lock(rwl, isc_rwlocktype_read);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ atomic_store_release(&rwl->downgrade, false);
+}
+
+void
+isc_rwlock_destroy(isc_rwlock_t *rwl) {
+ pthread_rwlock_destroy(&rwl->rwlock);
+}
+
+#else /* if USE_PTHREAD_RWLOCK */
+
+#define RWLOCK_MAGIC ISC_MAGIC('R', 'W', 'L', 'k')
+#define VALID_RWLOCK(rwl) ISC_MAGIC_VALID(rwl, RWLOCK_MAGIC)
+
+#ifndef RWLOCK_DEFAULT_READ_QUOTA
+#define RWLOCK_DEFAULT_READ_QUOTA 4
+#endif /* ifndef RWLOCK_DEFAULT_READ_QUOTA */
+
+#ifndef RWLOCK_DEFAULT_WRITE_QUOTA
+#define RWLOCK_DEFAULT_WRITE_QUOTA 4
+#endif /* ifndef RWLOCK_DEFAULT_WRITE_QUOTA */
+
+#ifndef RWLOCK_MAX_ADAPTIVE_COUNT
+#define RWLOCK_MAX_ADAPTIVE_COUNT 100
+#endif /* ifndef RWLOCK_MAX_ADAPTIVE_COUNT */
+
+#if defined(_MSC_VER)
+#include <intrin.h>
+#define isc_rwlock_pause() YieldProcessor()
+#elif defined(__x86_64__)
+#include <immintrin.h>
+#define isc_rwlock_pause() _mm_pause()
+#elif defined(__i386__)
+#define isc_rwlock_pause() __asm__ __volatile__("rep; nop")
+#elif defined(__ia64__)
+#define isc_rwlock_pause() __asm__ __volatile__("hint @pause")
+#elif defined(__arm__) && HAVE_ARM_YIELD
+#define isc_rwlock_pause() __asm__ __volatile__("yield")
+#elif defined(sun) && (defined(__sparc) || defined(__sparc__))
+#define isc_rwlock_pause() smt_pause()
+#elif (defined(__sparc) || defined(__sparc__)) && HAVE_SPARC_PAUSE
+#define isc_rwlock_pause() __asm__ __volatile__("pause")
+#elif defined(__ppc__) || defined(_ARCH_PPC) || defined(_ARCH_PWR) || \
+ defined(_ARCH_PWR2) || defined(_POWER)
+#define isc_rwlock_pause() __asm__ volatile("or 27,27,27")
+#else /* if defined(_MSC_VER) */
+#define isc_rwlock_pause()
+#endif /* if defined(_MSC_VER) */
+
+static isc_result_t
+isc__rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type);
+
+#ifdef ISC_RWLOCK_TRACE
+#include <stdio.h> /* Required for fprintf/stderr. */
+
+#include <isc/thread.h> /* Required for isc_thread_self(). */
+
+static void
+print_lock(const char *operation, isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ fprintf(stderr,
+ "rwlock %p thread %" PRIuPTR " %s(%s): "
+ "write_requests=%u, write_completions=%u, "
+ "cnt_and_flag=0x%x, readers_waiting=%u, "
+ "write_granted=%u, write_quota=%u\n",
+ rwl, isc_thread_self(), operation,
+ (type == isc_rwlocktype_read ? "read" : "write"),
+ atomic_load_acquire(&rwl->write_requests),
+ atomic_load_acquire(&rwl->write_completions),
+ atomic_load_acquire(&rwl->cnt_and_flag), rwl->readers_waiting,
+ atomic_load_acquire(&rwl->write_granted), rwl->write_quota);
+}
+#endif /* ISC_RWLOCK_TRACE */
+
+void
+isc_rwlock_init(isc_rwlock_t *rwl, unsigned int read_quota,
+ unsigned int write_quota) {
+ REQUIRE(rwl != NULL);
+
+ /*
+ * In case there's trouble initializing, we zero magic now. If all
+ * goes well, we'll set it to RWLOCK_MAGIC.
+ */
+ rwl->magic = 0;
+
+ atomic_init(&rwl->spins, 0);
+ atomic_init(&rwl->write_requests, 0);
+ atomic_init(&rwl->write_completions, 0);
+ atomic_init(&rwl->cnt_and_flag, 0);
+ rwl->readers_waiting = 0;
+ atomic_init(&rwl->write_granted, 0);
+ if (read_quota != 0) {
+ UNEXPECTED_ERROR("read quota is not supported");
+ }
+ if (write_quota == 0) {
+ write_quota = RWLOCK_DEFAULT_WRITE_QUOTA;
+ }
+ rwl->write_quota = write_quota;
+
+ isc_mutex_init(&rwl->lock);
+
+ isc_condition_init(&rwl->readable);
+ isc_condition_init(&rwl->writeable);
+
+ rwl->magic = RWLOCK_MAGIC;
+}
+
+void
+isc_rwlock_destroy(isc_rwlock_t *rwl) {
+ REQUIRE(VALID_RWLOCK(rwl));
+
+ REQUIRE(atomic_load_acquire(&rwl->write_requests) ==
+ atomic_load_acquire(&rwl->write_completions) &&
+ atomic_load_acquire(&rwl->cnt_and_flag) == 0 &&
+ rwl->readers_waiting == 0);
+
+ rwl->magic = 0;
+ (void)isc_condition_destroy(&rwl->readable);
+ (void)isc_condition_destroy(&rwl->writeable);
+ isc_mutex_destroy(&rwl->lock);
+}
+
+/*
+ * When some architecture-dependent atomic operations are available,
+ * rwlock can be more efficient than the generic algorithm defined below.
+ * The basic algorithm is described in the following URL:
+ * http://www.cs.rochester.edu/u/scott/synchronization/pseudocode/rw.html
+ *
+ * The key is to use the following integer variables modified atomically:
+ * write_requests, write_completions, and cnt_and_flag.
+ *
+ * write_requests and write_completions act as a waiting queue for writers
+ * in order to ensure the FIFO order. Both variables begin with the initial
+ * value of 0. When a new writer tries to get a write lock, it increments
+ * write_requests and gets the previous value of the variable as a "ticket".
+ * When write_completions reaches the ticket number, the new writer can start
+ * writing. When the writer completes its work, it increments
+ * write_completions so that another new writer can start working. If the
+ * write_requests is not equal to write_completions, it means a writer is now
+ * working or waiting. In this case, a new readers cannot start reading, or
+ * in other words, this algorithm basically prefers writers.
+ *
+ * cnt_and_flag is a "lock" shared by all readers and writers. This integer
+ * variable is a kind of structure with two members: writer_flag (1 bit) and
+ * reader_count (31 bits). The writer_flag shows whether a writer is working,
+ * and the reader_count shows the number of readers currently working or almost
+ * ready for working. A writer who has the current "ticket" tries to get the
+ * lock by exclusively setting the writer_flag to 1, provided that the whole
+ * 32-bit is 0 (meaning no readers or writers working). On the other hand,
+ * a new reader tries to increment the "reader_count" field provided that
+ * the writer_flag is 0 (meaning there is no writer working).
+ *
+ * If some of the above operations fail, the reader or the writer sleeps
+ * until the related condition changes. When a working reader or writer
+ * completes its work, some readers or writers are sleeping, and the condition
+ * that suspended the reader or writer has changed, it wakes up the sleeping
+ * readers or writers.
+ *
+ * As already noted, this algorithm basically prefers writers. In order to
+ * prevent readers from starving, however, the algorithm also introduces the
+ * "writer quota" (Q). When Q consecutive writers have completed their work,
+ * suspending readers, the last writer will wake up the readers, even if a new
+ * writer is waiting.
+ *
+ * Implementation specific note: due to the combination of atomic operations
+ * and a mutex lock, ordering between the atomic operation and locks can be
+ * very sensitive in some cases. In particular, it is generally very important
+ * to check the atomic variable that requires a reader or writer to sleep after
+ * locking the mutex and before actually sleeping; otherwise, it could be very
+ * likely to cause a deadlock. For example, assume "var" is a variable
+ * atomically modified, then the corresponding code would be:
+ * if (var == need_sleep) {
+ * LOCK(lock);
+ * if (var == need_sleep)
+ * WAIT(cond, lock);
+ * UNLOCK(lock);
+ * }
+ * The second check is important, since "var" is protected by the atomic
+ * operation, not by the mutex, and can be changed just before sleeping.
+ * (The first "if" could be omitted, but this is also important in order to
+ * make the code efficient by avoiding the use of the mutex unless it is
+ * really necessary.)
+ */
+
+#define WRITER_ACTIVE 0x1
+#define READER_INCR 0x2
+
+static isc_result_t
+isc__rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ int32_t cntflag;
+
+ REQUIRE(VALID_RWLOCK(rwl));
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("prelock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ if (type == isc_rwlocktype_read) {
+ if (atomic_load_acquire(&rwl->write_requests) !=
+ atomic_load_acquire(&rwl->write_completions))
+ {
+ /* there is a waiting or active writer */
+ LOCK(&rwl->lock);
+ if (atomic_load_acquire(&rwl->write_requests) !=
+ atomic_load_acquire(&rwl->write_completions))
+ {
+ rwl->readers_waiting++;
+ WAIT(&rwl->readable, &rwl->lock);
+ rwl->readers_waiting--;
+ }
+ UNLOCK(&rwl->lock);
+ }
+
+ cntflag = atomic_fetch_add_release(&rwl->cnt_and_flag,
+ READER_INCR);
+ POST(cntflag);
+ while (1) {
+ if ((atomic_load_acquire(&rwl->cnt_and_flag) &
+ WRITER_ACTIVE) == 0)
+ {
+ break;
+ }
+
+ /* A writer is still working */
+ LOCK(&rwl->lock);
+ rwl->readers_waiting++;
+ if ((atomic_load_acquire(&rwl->cnt_and_flag) &
+ WRITER_ACTIVE) != 0)
+ {
+ WAIT(&rwl->readable, &rwl->lock);
+ }
+ rwl->readers_waiting--;
+ UNLOCK(&rwl->lock);
+
+ /*
+ * Typically, the reader should be able to get a lock
+ * at this stage:
+ * (1) there should have been no pending writer when
+ * the reader was trying to increment the
+ * counter; otherwise, the writer should be in
+ * the waiting queue, preventing the reader from
+ * proceeding to this point.
+ * (2) once the reader increments the counter, no
+ * more writer can get a lock.
+ * Still, it is possible another writer can work at
+ * this point, e.g. in the following scenario:
+ * A previous writer unlocks the writer lock.
+ * This reader proceeds to point (1).
+ * A new writer appears, and gets a new lock before
+ * the reader increments the counter.
+ * The reader then increments the counter.
+ * The previous writer notices there is a waiting
+ * reader who is almost ready, and wakes it up.
+ * So, the reader needs to confirm whether it can now
+ * read explicitly (thus we loop). Note that this is
+ * not an infinite process, since the reader has
+ * incremented the counter at this point.
+ */
+ }
+
+ /*
+ * If we are temporarily preferred to writers due to the writer
+ * quota, reset the condition (race among readers doesn't
+ * matter).
+ */
+ atomic_store_release(&rwl->write_granted, 0);
+ } else {
+ int32_t prev_writer;
+
+ /* enter the waiting queue, and wait for our turn */
+ prev_writer = atomic_fetch_add_release(&rwl->write_requests, 1);
+ while (atomic_load_acquire(&rwl->write_completions) !=
+ prev_writer)
+ {
+ LOCK(&rwl->lock);
+ if (atomic_load_acquire(&rwl->write_completions) !=
+ prev_writer)
+ {
+ WAIT(&rwl->writeable, &rwl->lock);
+ UNLOCK(&rwl->lock);
+ continue;
+ }
+ UNLOCK(&rwl->lock);
+ break;
+ }
+
+ while (!atomic_compare_exchange_weak_acq_rel(
+ &rwl->cnt_and_flag, &(int_fast32_t){ 0 },
+ WRITER_ACTIVE))
+ {
+ /* Another active reader or writer is working. */
+ LOCK(&rwl->lock);
+ if (atomic_load_acquire(&rwl->cnt_and_flag) != 0) {
+ WAIT(&rwl->writeable, &rwl->lock);
+ }
+ UNLOCK(&rwl->lock);
+ }
+
+ INSIST((atomic_load_acquire(&rwl->cnt_and_flag) &
+ WRITER_ACTIVE));
+ atomic_fetch_add_release(&rwl->write_granted, 1);
+ }
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("postlock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_rwlock_lock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ int32_t cnt = 0;
+ int32_t spins = atomic_load_acquire(&rwl->spins) * 2 + 10;
+ int32_t max_cnt = ISC_MAX(spins, RWLOCK_MAX_ADAPTIVE_COUNT);
+ isc_result_t result = ISC_R_SUCCESS;
+
+ do {
+ if (cnt++ >= max_cnt) {
+ result = isc__rwlock_lock(rwl, type);
+ break;
+ }
+ isc_rwlock_pause();
+ } while (isc_rwlock_trylock(rwl, type) != ISC_R_SUCCESS);
+
+ atomic_fetch_add_release(&rwl->spins, (cnt - spins) / 8);
+
+ return (result);
+}
+
+isc_result_t
+isc_rwlock_trylock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ int32_t cntflag;
+
+ REQUIRE(VALID_RWLOCK(rwl));
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("prelock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ if (type == isc_rwlocktype_read) {
+ /* If a writer is waiting or working, we fail. */
+ if (atomic_load_acquire(&rwl->write_requests) !=
+ atomic_load_acquire(&rwl->write_completions))
+ {
+ return (ISC_R_LOCKBUSY);
+ }
+
+ /* Otherwise, be ready for reading. */
+ cntflag = atomic_fetch_add_release(&rwl->cnt_and_flag,
+ READER_INCR);
+ if ((cntflag & WRITER_ACTIVE) != 0) {
+ /*
+ * A writer is working. We lose, and cancel the read
+ * request.
+ */
+ cntflag = atomic_fetch_sub_release(&rwl->cnt_and_flag,
+ READER_INCR);
+ /*
+ * If no other readers are waiting and we've suspended
+ * new writers in this short period, wake them up.
+ */
+ if (cntflag == READER_INCR &&
+ atomic_load_acquire(&rwl->write_completions) !=
+ atomic_load_acquire(&rwl->write_requests))
+ {
+ LOCK(&rwl->lock);
+ BROADCAST(&rwl->writeable);
+ UNLOCK(&rwl->lock);
+ }
+
+ return (ISC_R_LOCKBUSY);
+ }
+ } else {
+ /* Try locking without entering the waiting queue. */
+ int_fast32_t zero = 0;
+ if (!atomic_compare_exchange_strong_acq_rel(
+ &rwl->cnt_and_flag, &zero, WRITER_ACTIVE))
+ {
+ return (ISC_R_LOCKBUSY);
+ }
+
+ /*
+ * XXXJT: jump into the queue, possibly breaking the writer
+ * order.
+ */
+ atomic_fetch_sub_release(&rwl->write_completions, 1);
+ atomic_fetch_add_release(&rwl->write_granted, 1);
+ }
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("postlock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_rwlock_tryupgrade(isc_rwlock_t *rwl) {
+ REQUIRE(VALID_RWLOCK(rwl));
+
+ int_fast32_t reader_incr = READER_INCR;
+
+ /* Try to acquire write access. */
+ atomic_compare_exchange_strong_acq_rel(&rwl->cnt_and_flag, &reader_incr,
+ WRITER_ACTIVE);
+ /*
+ * There must have been no writer, and there must have
+ * been at least one reader.
+ */
+ INSIST((reader_incr & WRITER_ACTIVE) == 0 &&
+ (reader_incr & ~WRITER_ACTIVE) != 0);
+
+ if (reader_incr == READER_INCR) {
+ /*
+ * We are the only reader and have been upgraded.
+ * Now jump into the head of the writer waiting queue.
+ */
+ atomic_fetch_sub_release(&rwl->write_completions, 1);
+ } else {
+ return (ISC_R_LOCKBUSY);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_rwlock_downgrade(isc_rwlock_t *rwl) {
+ int32_t prev_readers;
+
+ REQUIRE(VALID_RWLOCK(rwl));
+
+ /* Become an active reader. */
+ prev_readers = atomic_fetch_add_release(&rwl->cnt_and_flag,
+ READER_INCR);
+ /* We must have been a writer. */
+ INSIST((prev_readers & WRITER_ACTIVE) != 0);
+
+ /* Complete write */
+ atomic_fetch_sub_release(&rwl->cnt_and_flag, WRITER_ACTIVE);
+ atomic_fetch_add_release(&rwl->write_completions, 1);
+
+ /* Resume other readers */
+ LOCK(&rwl->lock);
+ if (rwl->readers_waiting > 0) {
+ BROADCAST(&rwl->readable);
+ }
+ UNLOCK(&rwl->lock);
+}
+
+isc_result_t
+isc_rwlock_unlock(isc_rwlock_t *rwl, isc_rwlocktype_t type) {
+ int32_t prev_cnt;
+
+ REQUIRE(VALID_RWLOCK(rwl));
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("preunlock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ if (type == isc_rwlocktype_read) {
+ prev_cnt = atomic_fetch_sub_release(&rwl->cnt_and_flag,
+ READER_INCR);
+ /*
+ * If we're the last reader and any writers are waiting, wake
+ * them up. We need to wake up all of them to ensure the
+ * FIFO order.
+ */
+ if (prev_cnt == READER_INCR &&
+ atomic_load_acquire(&rwl->write_completions) !=
+ atomic_load_acquire(&rwl->write_requests))
+ {
+ LOCK(&rwl->lock);
+ BROADCAST(&rwl->writeable);
+ UNLOCK(&rwl->lock);
+ }
+ } else {
+ bool wakeup_writers = true;
+
+ /*
+ * Reset the flag, and (implicitly) tell other writers
+ * we are done.
+ */
+ atomic_fetch_sub_release(&rwl->cnt_and_flag, WRITER_ACTIVE);
+ atomic_fetch_add_release(&rwl->write_completions, 1);
+
+ if ((atomic_load_acquire(&rwl->write_granted) >=
+ rwl->write_quota) ||
+ (atomic_load_acquire(&rwl->write_requests) ==
+ atomic_load_acquire(&rwl->write_completions)) ||
+ (atomic_load_acquire(&rwl->cnt_and_flag) & ~WRITER_ACTIVE))
+ {
+ /*
+ * We have passed the write quota, no writer is
+ * waiting, or some readers are almost ready, pending
+ * possible writers. Note that the last case can
+ * happen even if write_requests != write_completions
+ * (which means a new writer in the queue), so we need
+ * to catch the case explicitly.
+ */
+ LOCK(&rwl->lock);
+ if (rwl->readers_waiting > 0) {
+ wakeup_writers = false;
+ BROADCAST(&rwl->readable);
+ }
+ UNLOCK(&rwl->lock);
+ }
+
+ if ((atomic_load_acquire(&rwl->write_requests) !=
+ atomic_load_acquire(&rwl->write_completions)) &&
+ wakeup_writers)
+ {
+ LOCK(&rwl->lock);
+ BROADCAST(&rwl->writeable);
+ UNLOCK(&rwl->lock);
+ }
+ }
+
+#ifdef ISC_RWLOCK_TRACE
+ print_lock("postunlock", rwl, type);
+#endif /* ifdef ISC_RWLOCK_TRACE */
+
+ return (ISC_R_SUCCESS);
+}
+
+#endif /* USE_PTHREAD_RWLOCK */
diff --git a/lib/isc/safe.c b/lib/isc/safe.c
new file mode 100644
index 0000000..988034d
--- /dev/null
+++ b/lib/isc/safe.c
@@ -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.
+ */
+
+#include <openssl/crypto.h>
+
+#include <isc/safe.h>
+
+int
+isc_safe_memequal(const void *s1, const void *s2, size_t len) {
+ return (!CRYPTO_memcmp(s1, s2, len));
+}
+
+void
+isc_safe_memwipe(void *ptr, size_t len) {
+ OPENSSL_cleanse(ptr, len);
+}
diff --git a/lib/isc/serial.c b/lib/isc/serial.c
new file mode 100644
index 0000000..5ede64b
--- /dev/null
+++ b/lib/isc/serial.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/serial.h>
+
+bool
+isc_serial_lt(uint32_t a, uint32_t b) {
+ /*
+ * Undefined => false
+ */
+ if (a == (b ^ 0x80000000U)) {
+ return (false);
+ }
+ return (((int32_t)(a - b) < 0) ? true : false);
+}
+
+bool
+isc_serial_gt(uint32_t a, uint32_t b) {
+ return (((int32_t)(a - b) > 0) ? true : false);
+}
+
+bool
+isc_serial_le(uint32_t a, uint32_t b) {
+ return ((a == b) ? true : isc_serial_lt(a, b));
+}
+
+bool
+isc_serial_ge(uint32_t a, uint32_t b) {
+ return ((a == b) ? true : isc_serial_gt(a, b));
+}
+
+bool
+isc_serial_eq(uint32_t a, uint32_t b) {
+ return ((a == b) ? true : false);
+}
+
+bool
+isc_serial_ne(uint32_t a, uint32_t b) {
+ return ((a != b) ? true : false);
+}
diff --git a/lib/isc/siphash.c b/lib/isc/siphash.c
new file mode 100644
index 0000000..a6e60cf
--- /dev/null
+++ b/lib/isc/siphash.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.
+ */
+
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <isc/endian.h>
+#include <isc/siphash.h>
+#include <isc/util.h>
+
+/*
+ * The implementation is based on SipHash reference C implementation by
+ *
+ * Copyright (c) 2012-2016 Jean-Philippe Aumasson
+ * <jeanphilippe.aumasson@gmail.com> Copyright (c) 2012-2014 Daniel J. Bernstein
+ * <djb@cr.yp.to>
+ *
+ * To the extent possible under law, the author(s) have dedicated all copyright
+ * and related and neighboring rights to this software to the public domain
+ * worldwide. This software is distributed without any warranty. You should
+ * have received a copy of the CC0 Public Domain Dedication along with this
+ * software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
+ */
+
+#define cROUNDS 2
+#define dROUNDS 4
+
+#define ROTATE64(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
+
+#define HALF_ROUND64(a, b, c, d, s, t) \
+ a += b; \
+ c += d; \
+ b = ROTATE64(b, s) ^ a; \
+ d = ROTATE64(d, t) ^ c; \
+ a = ROTATE64(a, 32);
+
+#define FULL_ROUND64(v0, v1, v2, v3) \
+ HALF_ROUND64(v0, v1, v2, v3, 13, 16); \
+ HALF_ROUND64(v2, v1, v0, v3, 17, 21);
+
+#define SIPROUND FULL_ROUND64
+
+#define ROTATE32(x, b) (uint32_t)(((x) << (b)) | ((x) >> (32 - (b))))
+
+#define HALF_ROUND32(a, b, c, d, s, t) \
+ a += b; \
+ c += d; \
+ b = ROTATE32(b, s) ^ a; \
+ d = ROTATE32(d, t) ^ c; \
+ a = ROTATE32(a, 16);
+
+#define FULL_ROUND32(v0, v1, v2, v3) \
+ HALF_ROUND32(v0, v1, v2, v3, 5, 8); \
+ HALF_ROUND32(v2, v1, v0, v3, 13, 7);
+
+#define HALFSIPROUND FULL_ROUND32
+
+#define U32TO8_LE(p, v) \
+ (p)[0] = (uint8_t)((v)); \
+ (p)[1] = (uint8_t)((v) >> 8); \
+ (p)[2] = (uint8_t)((v) >> 16); \
+ (p)[3] = (uint8_t)((v) >> 24);
+
+#define U8TO32_LE(p) \
+ (((uint32_t)((p)[0])) | ((uint32_t)((p)[1]) << 8) | \
+ ((uint32_t)((p)[2]) << 16) | ((uint32_t)((p)[3]) << 24))
+
+#define U64TO8_LE(p, v) \
+ U32TO8_LE((p), (uint32_t)((v))); \
+ U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
+
+#define U8TO64_LE(p) \
+ (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \
+ ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \
+ ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \
+ ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
+
+void
+isc_siphash24(const uint8_t *k, const uint8_t *in, const size_t inlen,
+ uint8_t *out) {
+ REQUIRE(k != NULL);
+ REQUIRE(out != NULL);
+ REQUIRE(inlen == 0 || in != NULL);
+
+ uint64_t k0 = U8TO64_LE(k);
+ uint64_t k1 = U8TO64_LE(k + 8);
+
+ uint64_t v0 = UINT64_C(0x736f6d6570736575) ^ k0;
+ uint64_t v1 = UINT64_C(0x646f72616e646f6d) ^ k1;
+ uint64_t v2 = UINT64_C(0x6c7967656e657261) ^ k0;
+ uint64_t v3 = UINT64_C(0x7465646279746573) ^ k1;
+
+ uint64_t b = ((uint64_t)inlen) << 56;
+
+ const uint8_t *end = (in == NULL)
+ ? NULL
+ : in + inlen - (inlen % sizeof(uint64_t));
+ const size_t left = inlen & 7;
+
+ for (; in != end; in += 8) {
+ uint64_t m = U8TO64_LE(in);
+
+ v3 ^= m;
+
+ for (size_t i = 0; i < cROUNDS; ++i) {
+ SIPROUND(v0, v1, v2, v3);
+ }
+
+ v0 ^= m;
+ }
+
+ switch (left) {
+ case 7:
+ b |= ((uint64_t)in[6]) << 48;
+ FALLTHROUGH;
+ case 6:
+ b |= ((uint64_t)in[5]) << 40;
+ FALLTHROUGH;
+ case 5:
+ b |= ((uint64_t)in[4]) << 32;
+ FALLTHROUGH;
+ case 4:
+ b |= ((uint64_t)in[3]) << 24;
+ FALLTHROUGH;
+ case 3:
+ b |= ((uint64_t)in[2]) << 16;
+ FALLTHROUGH;
+ case 2:
+ b |= ((uint64_t)in[1]) << 8;
+ FALLTHROUGH;
+ case 1:
+ b |= ((uint64_t)in[0]);
+ FALLTHROUGH;
+ case 0:
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ v3 ^= b;
+
+ for (size_t i = 0; i < cROUNDS; ++i) {
+ SIPROUND(v0, v1, v2, v3);
+ }
+
+ v0 ^= b;
+
+ v2 ^= 0xff;
+
+ for (size_t i = 0; i < dROUNDS; ++i) {
+ SIPROUND(v0, v1, v2, v3);
+ }
+
+ b = v0 ^ v1 ^ v2 ^ v3;
+
+ U64TO8_LE(out, b);
+}
+
+void
+isc_halfsiphash24(const uint8_t *k, const uint8_t *in, const size_t inlen,
+ uint8_t *out) {
+ REQUIRE(k != NULL);
+ REQUIRE(out != NULL);
+ REQUIRE(inlen == 0 || in != NULL);
+
+ uint32_t k0 = U8TO32_LE(k);
+ uint32_t k1 = U8TO32_LE(k + 4);
+
+ uint32_t v0 = UINT32_C(0x00000000) ^ k0;
+ uint32_t v1 = UINT32_C(0x00000000) ^ k1;
+ uint32_t v2 = UINT32_C(0x6c796765) ^ k0;
+ uint32_t v3 = UINT32_C(0x74656462) ^ k1;
+
+ uint32_t b = ((uint32_t)inlen) << 24;
+
+ const uint8_t *end = (in == NULL)
+ ? NULL
+ : in + inlen - (inlen % sizeof(uint32_t));
+ const int left = inlen & 3;
+
+ for (; in != end; in += 4) {
+ uint32_t m = U8TO32_LE(in);
+ v3 ^= m;
+
+ for (size_t i = 0; i < cROUNDS; ++i) {
+ HALFSIPROUND(v0, v1, v2, v3);
+ }
+
+ v0 ^= m;
+ }
+
+ switch (left) {
+ case 3:
+ b |= ((uint32_t)in[2]) << 16;
+ FALLTHROUGH;
+ case 2:
+ b |= ((uint32_t)in[1]) << 8;
+ FALLTHROUGH;
+ case 1:
+ b |= ((uint32_t)in[0]);
+ FALLTHROUGH;
+ case 0:
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ v3 ^= b;
+
+ for (size_t i = 0; i < cROUNDS; ++i) {
+ HALFSIPROUND(v0, v1, v2, v3);
+ }
+
+ v0 ^= b;
+
+ v2 ^= 0xff;
+
+ for (size_t i = 0; i < dROUNDS; ++i) {
+ HALFSIPROUND(v0, v1, v2, v3);
+ }
+
+ b = v1 ^ v3;
+ U32TO8_LE(out, b);
+}
diff --git a/lib/isc/sockaddr.c b/lib/isc/sockaddr.c
new file mode 100644
index 0000000..038e3ec
--- /dev/null
+++ b/lib/isc/sockaddr.c
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/buffer.h>
+#include <isc/hash.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+bool
+isc_sockaddr_equal(const isc_sockaddr_t *a, const isc_sockaddr_t *b) {
+ return (isc_sockaddr_compare(a, b,
+ ISC_SOCKADDR_CMPADDR |
+ ISC_SOCKADDR_CMPPORT |
+ ISC_SOCKADDR_CMPSCOPE));
+}
+
+bool
+isc_sockaddr_eqaddr(const isc_sockaddr_t *a, const isc_sockaddr_t *b) {
+ return (isc_sockaddr_compare(
+ a, b, ISC_SOCKADDR_CMPADDR | ISC_SOCKADDR_CMPSCOPE));
+}
+
+bool
+isc_sockaddr_compare(const isc_sockaddr_t *a, const isc_sockaddr_t *b,
+ unsigned int flags) {
+ REQUIRE(a != NULL && b != NULL);
+
+ if (a->length != b->length) {
+ return (false);
+ }
+
+ /*
+ * We don't just memcmp because the sin_zero field isn't always
+ * zero.
+ */
+
+ if (a->type.sa.sa_family != b->type.sa.sa_family) {
+ return (false);
+ }
+ switch (a->type.sa.sa_family) {
+ case AF_INET:
+ if ((flags & ISC_SOCKADDR_CMPADDR) != 0 &&
+ memcmp(&a->type.sin.sin_addr, &b->type.sin.sin_addr,
+ sizeof(a->type.sin.sin_addr)) != 0)
+ {
+ return (false);
+ }
+ if ((flags & ISC_SOCKADDR_CMPPORT) != 0 &&
+ a->type.sin.sin_port != b->type.sin.sin_port)
+ {
+ return (false);
+ }
+ break;
+ case AF_INET6:
+ if ((flags & ISC_SOCKADDR_CMPADDR) != 0 &&
+ memcmp(&a->type.sin6.sin6_addr, &b->type.sin6.sin6_addr,
+ sizeof(a->type.sin6.sin6_addr)) != 0)
+ {
+ return (false);
+ }
+ /*
+ * If ISC_SOCKADDR_CMPSCOPEZERO is set then don't return
+ * false if one of the scopes in zero.
+ */
+ if ((flags & ISC_SOCKADDR_CMPSCOPE) != 0 &&
+ a->type.sin6.sin6_scope_id != b->type.sin6.sin6_scope_id &&
+ ((flags & ISC_SOCKADDR_CMPSCOPEZERO) == 0 ||
+ (a->type.sin6.sin6_scope_id != 0 &&
+ b->type.sin6.sin6_scope_id != 0)))
+ {
+ return (false);
+ }
+ if ((flags & ISC_SOCKADDR_CMPPORT) != 0 &&
+ a->type.sin6.sin6_port != b->type.sin6.sin6_port)
+ {
+ return (false);
+ }
+ break;
+ default:
+ if (memcmp(&a->type, &b->type, a->length) != 0) {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+bool
+isc_sockaddr_eqaddrprefix(const isc_sockaddr_t *a, const isc_sockaddr_t *b,
+ unsigned int prefixlen) {
+ isc_netaddr_t na, nb;
+ isc_netaddr_fromsockaddr(&na, a);
+ isc_netaddr_fromsockaddr(&nb, b);
+ return (isc_netaddr_eqprefix(&na, &nb, prefixlen));
+}
+
+isc_result_t
+isc_sockaddr_totext(const isc_sockaddr_t *sockaddr, isc_buffer_t *target) {
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ char pbuf[sizeof("65000")];
+ unsigned int plen;
+ isc_region_t avail;
+
+ REQUIRE(sockaddr != NULL);
+
+ /*
+ * Do the port first, giving us the opportunity to check for
+ * unsupported address families before calling
+ * isc_netaddr_fromsockaddr().
+ */
+ switch (sockaddr->type.sa.sa_family) {
+ case AF_INET:
+ snprintf(pbuf, sizeof(pbuf), "%u",
+ ntohs(sockaddr->type.sin.sin_port));
+ break;
+ case AF_INET6:
+ snprintf(pbuf, sizeof(pbuf), "%u",
+ ntohs(sockaddr->type.sin6.sin6_port));
+ break;
+ case AF_UNIX:
+ plen = strlen(sockaddr->type.sunix.sun_path);
+ if (plen >= isc_buffer_availablelength(target)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putmem(
+ target,
+ (const unsigned char *)sockaddr->type.sunix.sun_path,
+ plen);
+
+ /*
+ * Null terminate after used region.
+ */
+ isc_buffer_availableregion(target, &avail);
+ INSIST(avail.length >= 1);
+ avail.base[0] = '\0';
+
+ return (ISC_R_SUCCESS);
+ default:
+ return (ISC_R_FAILURE);
+ }
+
+ plen = strlen(pbuf);
+ INSIST(plen < sizeof(pbuf));
+
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ result = isc_netaddr_totext(&netaddr, target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (1 + plen + 1 > isc_buffer_availablelength(target)) {
+ return (ISC_R_NOSPACE);
+ }
+
+ isc_buffer_putmem(target, (const unsigned char *)"#", 1);
+ isc_buffer_putmem(target, (const unsigned char *)pbuf, plen);
+
+ /*
+ * Null terminate after used region.
+ */
+ isc_buffer_availableregion(target, &avail);
+ INSIST(avail.length >= 1);
+ avail.base[0] = '\0';
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_sockaddr_format(const isc_sockaddr_t *sa, char *array, unsigned int size) {
+ isc_result_t result;
+ isc_buffer_t buf;
+
+ if (size == 0U) {
+ return;
+ }
+
+ isc_buffer_init(&buf, array, size);
+ result = isc_sockaddr_totext(sa, &buf);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * The message is the same as in netaddr.c.
+ */
+ snprintf(array, size, "<unknown address, family %u>",
+ sa->type.sa.sa_family);
+ array[size - 1] = '\0';
+ }
+}
+
+unsigned int
+isc_sockaddr_hash(const isc_sockaddr_t *sockaddr, bool address_only) {
+ unsigned int length = 0;
+ const unsigned char *s = NULL;
+ unsigned int h = 0;
+ unsigned int p = 0;
+ const struct in6_addr *in6;
+
+ REQUIRE(sockaddr != NULL);
+
+ switch (sockaddr->type.sa.sa_family) {
+ case AF_INET:
+ s = (const unsigned char *)&sockaddr->type.sin.sin_addr;
+ p = ntohs(sockaddr->type.sin.sin_port);
+ length = sizeof(sockaddr->type.sin.sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ in6 = &sockaddr->type.sin6.sin6_addr;
+ s = (const unsigned char *)in6;
+ if (IN6_IS_ADDR_V4MAPPED(in6)) {
+ s += 12;
+ length = sizeof(sockaddr->type.sin.sin_addr.s_addr);
+ } else {
+ length = sizeof(sockaddr->type.sin6.sin6_addr);
+ }
+ p = ntohs(sockaddr->type.sin6.sin6_port);
+ break;
+ default:
+ UNEXPECTED_ERROR("unknown address family: %d",
+ (int)sockaddr->type.sa.sa_family);
+ s = (const unsigned char *)&sockaddr->type;
+ length = sockaddr->length;
+ p = 0;
+ }
+
+ uint8_t buf[sizeof(struct sockaddr_storage) + sizeof(p)];
+ memmove(buf, s, length);
+ if (!address_only) {
+ memmove(buf + length, &p, sizeof(p));
+ h = isc_hash_function(buf, length + sizeof(p), true);
+ } else {
+ h = isc_hash_function(buf, length, true);
+ }
+
+ return (h);
+}
+
+void
+isc_sockaddr_any(isc_sockaddr_t *sockaddr) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin.sin_family = AF_INET;
+ sockaddr->type.sin.sin_addr.s_addr = INADDR_ANY;
+ sockaddr->type.sin.sin_port = 0;
+ sockaddr->length = sizeof(sockaddr->type.sin);
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+void
+isc_sockaddr_any6(isc_sockaddr_t *sockaddr) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin6.sin6_family = AF_INET6;
+ sockaddr->type.sin6.sin6_addr = in6addr_any;
+ sockaddr->type.sin6.sin6_port = 0;
+ sockaddr->length = sizeof(sockaddr->type.sin6);
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+void
+isc_sockaddr_fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina,
+ in_port_t port) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin.sin_family = AF_INET;
+ sockaddr->type.sin.sin_addr = *ina;
+ sockaddr->type.sin.sin_port = htons(port);
+ sockaddr->length = sizeof(sockaddr->type.sin);
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+void
+isc_sockaddr_anyofpf(isc_sockaddr_t *sockaddr, int pf) {
+ switch (pf) {
+ case AF_INET:
+ isc_sockaddr_any(sockaddr);
+ break;
+ case AF_INET6:
+ isc_sockaddr_any6(sockaddr);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+void
+isc_sockaddr_fromin6(isc_sockaddr_t *sockaddr, const struct in6_addr *ina6,
+ in_port_t port) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin6.sin6_family = AF_INET6;
+ sockaddr->type.sin6.sin6_addr = *ina6;
+ sockaddr->type.sin6.sin6_port = htons(port);
+ sockaddr->length = sizeof(sockaddr->type.sin6);
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+void
+isc_sockaddr_v6fromin(isc_sockaddr_t *sockaddr, const struct in_addr *ina,
+ in_port_t port) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin6.sin6_family = AF_INET6;
+ sockaddr->type.sin6.sin6_addr.s6_addr[10] = 0xff;
+ sockaddr->type.sin6.sin6_addr.s6_addr[11] = 0xff;
+ memmove(&sockaddr->type.sin6.sin6_addr.s6_addr[12], ina, 4);
+ sockaddr->type.sin6.sin6_port = htons(port);
+ sockaddr->length = sizeof(sockaddr->type.sin6);
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+int
+isc_sockaddr_pf(const isc_sockaddr_t *sockaddr) {
+ /*
+ * Get the protocol family of 'sockaddr'.
+ */
+
+#if (AF_INET == PF_INET && AF_INET6 == PF_INET6)
+ /*
+ * Assume that PF_xxx == AF_xxx for all AF and PF.
+ */
+ return (sockaddr->type.sa.sa_family);
+#else /* if (AF_INET == PF_INET && AF_INET6 == PF_INET6) */
+ switch (sockaddr->type.sa.sa_family) {
+ case AF_INET:
+ return (PF_INET);
+ case AF_INET6:
+ return (PF_INET6);
+ default:
+ FATAL_ERROR("unknown address family: %d",
+ (int)sockaddr->type.sa.sa_family);
+ }
+#endif /* if (AF_INET == PF_INET && AF_INET6 == PF_INET6) */
+}
+
+void
+isc_sockaddr_fromnetaddr(isc_sockaddr_t *sockaddr, const isc_netaddr_t *na,
+ in_port_t port) {
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->type.sin.sin_family = na->family;
+ switch (na->family) {
+ case AF_INET:
+ sockaddr->length = sizeof(sockaddr->type.sin);
+ sockaddr->type.sin.sin_addr = na->type.in;
+ sockaddr->type.sin.sin_port = htons(port);
+ break;
+ case AF_INET6:
+ sockaddr->length = sizeof(sockaddr->type.sin6);
+ memmove(&sockaddr->type.sin6.sin6_addr, &na->type.in6, 16);
+ sockaddr->type.sin6.sin6_scope_id = isc_netaddr_getzone(na);
+ sockaddr->type.sin6.sin6_port = htons(port);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ ISC_LINK_INIT(sockaddr, link);
+}
+
+void
+isc_sockaddr_setport(isc_sockaddr_t *sockaddr, in_port_t port) {
+ switch (sockaddr->type.sa.sa_family) {
+ case AF_INET:
+ sockaddr->type.sin.sin_port = htons(port);
+ break;
+ case AF_INET6:
+ sockaddr->type.sin6.sin6_port = htons(port);
+ break;
+ default:
+ FATAL_ERROR("unknown address family: %d",
+ (int)sockaddr->type.sa.sa_family);
+ }
+}
+
+in_port_t
+isc_sockaddr_getport(const isc_sockaddr_t *sockaddr) {
+ in_port_t port = 0;
+
+ switch (sockaddr->type.sa.sa_family) {
+ case AF_INET:
+ port = ntohs(sockaddr->type.sin.sin_port);
+ break;
+ case AF_INET6:
+ port = ntohs(sockaddr->type.sin6.sin6_port);
+ break;
+ default:
+ FATAL_ERROR("unknown address family: %d",
+ (int)sockaddr->type.sa.sa_family);
+ }
+
+ return (port);
+}
+
+bool
+isc_sockaddr_ismulticast(const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+
+ if (sockaddr->type.sa.sa_family == AF_INET ||
+ sockaddr->type.sa.sa_family == AF_INET6)
+ {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ return (isc_netaddr_ismulticast(&netaddr));
+ }
+ return (false);
+}
+
+bool
+isc_sockaddr_isexperimental(const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+
+ if (sockaddr->type.sa.sa_family == AF_INET) {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ return (isc_netaddr_isexperimental(&netaddr));
+ }
+ return (false);
+}
+
+bool
+isc_sockaddr_issitelocal(const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+
+ if (sockaddr->type.sa.sa_family == AF_INET6) {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ return (isc_netaddr_issitelocal(&netaddr));
+ }
+ return (false);
+}
+
+bool
+isc_sockaddr_islinklocal(const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+
+ if (sockaddr->type.sa.sa_family == AF_INET6) {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ return (isc_netaddr_islinklocal(&netaddr));
+ }
+ return (false);
+}
+
+bool
+isc_sockaddr_isnetzero(const isc_sockaddr_t *sockaddr) {
+ isc_netaddr_t netaddr;
+
+ if (sockaddr->type.sa.sa_family == AF_INET) {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ return (isc_netaddr_isnetzero(&netaddr));
+ }
+ return (false);
+}
+
+isc_result_t
+isc_sockaddr_frompath(isc_sockaddr_t *sockaddr, const char *path) {
+ if (strlen(path) >= sizeof(sockaddr->type.sunix.sun_path)) {
+ return (ISC_R_NOSPACE);
+ }
+ memset(sockaddr, 0, sizeof(*sockaddr));
+ sockaddr->length = sizeof(sockaddr->type.sunix);
+ sockaddr->type.sunix.sun_family = AF_UNIX;
+ strlcpy(sockaddr->type.sunix.sun_path, path,
+ sizeof(sockaddr->type.sunix.sun_path));
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_sockaddr_fromsockaddr(isc_sockaddr_t *isa, const struct sockaddr *sa) {
+ unsigned int length = 0;
+
+ switch (sa->sa_family) {
+ case AF_INET:
+ length = sizeof(isa->type.sin);
+ break;
+ case AF_INET6:
+ length = sizeof(isa->type.sin6);
+ break;
+ case AF_UNIX:
+ length = sizeof(isa->type.sunix);
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ memset(isa, 0, sizeof(isc_sockaddr_t));
+ memmove(isa, sa, length);
+ isa->length = length;
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/stats.c b/lib/isc/stats.c
new file mode 100644
index 0000000..3e4676c
--- /dev/null
+++ b/lib/isc/stats.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/atomic.h>
+#include <isc/buffer.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/refcount.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#define ISC_STATS_MAGIC ISC_MAGIC('S', 't', 'a', 't')
+#define ISC_STATS_VALID(x) ISC_MAGIC_VALID(x, ISC_STATS_MAGIC)
+
+typedef atomic_int_fast64_t isc__atomic_statcounter_t;
+
+struct isc_stats {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_refcount_t references;
+ int ncounters;
+ isc__atomic_statcounter_t *counters;
+};
+
+static isc_result_t
+create_stats(isc_mem_t *mctx, int ncounters, isc_stats_t **statsp) {
+ isc_stats_t *stats;
+ size_t counters_alloc_size;
+
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ stats = isc_mem_get(mctx, sizeof(*stats));
+ counters_alloc_size = sizeof(isc__atomic_statcounter_t) * ncounters;
+ stats->counters = isc_mem_get(mctx, counters_alloc_size);
+ isc_refcount_init(&stats->references, 1);
+ for (int i = 0; i < ncounters; i++) {
+ atomic_init(&stats->counters[i], 0);
+ }
+ stats->mctx = NULL;
+ isc_mem_attach(mctx, &stats->mctx);
+ stats->ncounters = ncounters;
+ stats->magic = ISC_STATS_MAGIC;
+ *statsp = stats;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_stats_attach(isc_stats_t *stats, isc_stats_t **statsp) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ isc_refcount_increment(&stats->references);
+ *statsp = stats;
+}
+
+void
+isc_stats_detach(isc_stats_t **statsp) {
+ isc_stats_t *stats;
+
+ REQUIRE(statsp != NULL && ISC_STATS_VALID(*statsp));
+
+ stats = *statsp;
+ *statsp = NULL;
+
+ if (isc_refcount_decrement(&stats->references) == 1) {
+ isc_refcount_destroy(&stats->references);
+ isc_mem_put(stats->mctx, stats->counters,
+ sizeof(isc__atomic_statcounter_t) *
+ stats->ncounters);
+ isc_mem_putanddetach(&stats->mctx, stats, sizeof(*stats));
+ }
+}
+
+int
+isc_stats_ncounters(isc_stats_t *stats) {
+ REQUIRE(ISC_STATS_VALID(stats));
+
+ return (stats->ncounters);
+}
+
+isc_result_t
+isc_stats_create(isc_mem_t *mctx, isc_stats_t **statsp, int ncounters) {
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ return (create_stats(mctx, ncounters, statsp));
+}
+
+void
+isc_stats_increment(isc_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(counter < stats->ncounters);
+
+ atomic_fetch_add_relaxed(&stats->counters[counter], 1);
+}
+
+void
+isc_stats_decrement(isc_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(counter < stats->ncounters);
+ atomic_fetch_sub_release(&stats->counters[counter], 1);
+}
+
+void
+isc_stats_dump(isc_stats_t *stats, isc_stats_dumper_t dump_fn, void *arg,
+ unsigned int options) {
+ int i;
+
+ REQUIRE(ISC_STATS_VALID(stats));
+
+ for (i = 0; i < stats->ncounters; i++) {
+ uint32_t counter = atomic_load_acquire(&stats->counters[i]);
+ if ((options & ISC_STATSDUMP_VERBOSE) == 0 && counter == 0) {
+ continue;
+ }
+ dump_fn((isc_statscounter_t)i, counter, arg);
+ }
+}
+
+void
+isc_stats_set(isc_stats_t *stats, uint64_t val, isc_statscounter_t counter) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(counter < stats->ncounters);
+
+ atomic_store_release(&stats->counters[counter], val);
+}
+
+void
+isc_stats_update_if_greater(isc_stats_t *stats, isc_statscounter_t counter,
+ isc_statscounter_t value) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(counter < stats->ncounters);
+
+ isc_statscounter_t curr_value =
+ atomic_load_acquire(&stats->counters[counter]);
+ do {
+ if (curr_value >= value) {
+ break;
+ }
+ } while (!atomic_compare_exchange_weak_acq_rel(
+ &stats->counters[counter], &curr_value, value));
+}
+
+isc_statscounter_t
+isc_stats_get_counter(isc_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(ISC_STATS_VALID(stats));
+ REQUIRE(counter < stats->ncounters);
+
+ return (atomic_load_acquire(&stats->counters[counter]));
+}
+
+void
+isc_stats_resize(isc_stats_t **statsp, int ncounters) {
+ isc_stats_t *stats;
+ size_t counters_alloc_size;
+ isc__atomic_statcounter_t *newcounters;
+
+ REQUIRE(statsp != NULL && *statsp != NULL);
+ REQUIRE(ISC_STATS_VALID(*statsp));
+ REQUIRE(ncounters > 0);
+
+ stats = *statsp;
+ if (stats->ncounters >= ncounters) {
+ /* We already have enough counters. */
+ return;
+ }
+
+ /* Grow number of counters. */
+ counters_alloc_size = sizeof(isc__atomic_statcounter_t) * ncounters;
+ newcounters = isc_mem_get(stats->mctx, counters_alloc_size);
+ for (int i = 0; i < ncounters; i++) {
+ atomic_init(&newcounters[i], 0);
+ }
+ for (int i = 0; i < stats->ncounters; i++) {
+ uint32_t counter = atomic_load_acquire(&stats->counters[i]);
+ atomic_store_release(&newcounters[i], counter);
+ }
+ isc_mem_put(stats->mctx, stats->counters,
+ sizeof(isc__atomic_statcounter_t) * stats->ncounters);
+ stats->counters = newcounters;
+ stats->ncounters = ncounters;
+}
diff --git a/lib/isc/stdio.c b/lib/isc/stdio.c
new file mode 100644
index 0000000..12ab678
--- /dev/null
+++ b/lib/isc/stdio.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <isc/stat.h>
+#include <isc/stdio.h>
+#include <isc/util.h>
+
+#include "errno2result.h"
+
+isc_result_t
+isc_stdio_open(const char *filename, const char *mode, FILE **fp) {
+ FILE *f;
+
+ f = fopen(filename, mode);
+ if (f == NULL) {
+ return (isc__errno2result(errno));
+ }
+ *fp = f;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_stdio_close(FILE *f) {
+ int r;
+
+ r = fclose(f);
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_stdio_seek(FILE *f, off_t offset, int whence) {
+ int r;
+
+ r = fseeko(f, offset, whence);
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_stdio_tell(FILE *f, off_t *offsetp) {
+ off_t r;
+
+ REQUIRE(offsetp != NULL);
+
+ r = ftello(f);
+ if (r >= 0) {
+ *offsetp = r;
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+isc_result_t
+isc_stdio_read(void *ptr, size_t size, size_t nmemb, FILE *f, size_t *nret) {
+ isc_result_t result = ISC_R_SUCCESS;
+ size_t r;
+
+ clearerr(f);
+ r = fread(ptr, size, nmemb, f);
+ if (r != nmemb) {
+ if (feof(f)) {
+ result = ISC_R_EOF;
+ } else {
+ result = isc__errno2result(errno);
+ }
+ }
+ if (nret != NULL) {
+ *nret = r;
+ }
+ return (result);
+}
+
+isc_result_t
+isc_stdio_write(const void *ptr, size_t size, size_t nmemb, FILE *f,
+ size_t *nret) {
+ isc_result_t result = ISC_R_SUCCESS;
+ size_t r;
+
+ clearerr(f);
+ r = fwrite(ptr, size, nmemb, f);
+ if (r != nmemb) {
+ result = isc__errno2result(errno);
+ }
+ if (nret != NULL) {
+ *nret = r;
+ }
+ return (result);
+}
+
+isc_result_t
+isc_stdio_flush(FILE *f) {
+ int r;
+
+ r = fflush(f);
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
+
+/*
+ * OpenBSD has deprecated ENOTSUP in favor of EOPNOTSUPP.
+ */
+#if defined(EOPNOTSUPP) && !defined(ENOTSUP)
+#define ENOTSUP EOPNOTSUPP
+#endif /* if defined(EOPNOTSUPP) && !defined(ENOTSUP) */
+
+isc_result_t
+isc_stdio_sync(FILE *f) {
+ struct stat buf;
+ int r;
+
+ if (fstat(fileno(f), &buf) != 0) {
+ return (isc__errno2result(errno));
+ }
+
+ /*
+ * Only call fsync() on regular files.
+ */
+ if ((buf.st_mode & S_IFMT) != S_IFREG) {
+ return (ISC_R_SUCCESS);
+ }
+
+ r = fsync(fileno(f));
+ if (r == 0) {
+ return (ISC_R_SUCCESS);
+ } else {
+ return (isc__errno2result(errno));
+ }
+}
diff --git a/lib/isc/stdtime.c b/lib/isc/stdtime.c
new file mode 100644
index 0000000..b7cec81
--- /dev/null
+++ b/lib/isc/stdtime.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h> /* NULL */
+#include <stdlib.h> /* NULL */
+#include <syslog.h>
+#include <time.h>
+
+#include <isc/stdtime.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#if defined(CLOCK_REALTIME_COARSE)
+#define CLOCKSOURCE CLOCK_REALTIME_COARSE
+#elif defined(CLOCK_REALTIME_FAST)
+#define CLOCKSOURCE CLOCK_REALTIME_FAST
+#else /* if defined(CLOCK_REALTIME_COARSE) */
+#define CLOCKSOURCE CLOCK_REALTIME
+#endif /* if defined(CLOCK_REALTIME_COARSE) */
+
+void
+isc_stdtime_get(isc_stdtime_t *t) {
+ REQUIRE(t != NULL);
+
+ struct timespec ts;
+
+ if (clock_gettime(CLOCKSOURCE, &ts) == -1) {
+ FATAL_SYSERROR(errno, "clock_gettime()");
+ }
+
+ REQUIRE(ts.tv_sec > 0 && ts.tv_nsec >= 0 && ts.tv_nsec < NS_PER_SEC);
+
+ *t = (isc_stdtime_t)ts.tv_sec;
+}
+
+void
+isc_stdtime_tostring(isc_stdtime_t t, char *out, size_t outlen) {
+ time_t when;
+
+ REQUIRE(out != NULL);
+ REQUIRE(outlen >= 26);
+
+ UNUSED(outlen);
+
+ /* time_t and isc_stdtime_t might be different sizes */
+ when = t;
+ INSIST((ctime_r(&when, out) != NULL));
+ *(out + strlen(out) - 1) = '\0';
+}
diff --git a/lib/isc/string.c b/lib/isc/string.c
new file mode 100644
index 0000000..09cf5d6
--- /dev/null
+++ b/lib/isc/string.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001 Mike Barcroft <mike@FreeBSD.org>
+ * Copyright (c) 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#ifdef _GNU_SOURCE
+#undef _GNU_SOURCE
+#endif /* ifdef _GNU_SOURCE */
+#include <string.h>
+
+#include <isc/string.h> /* IWYU pragma: keep */
+
+#if !defined(HAVE_STRLCPY)
+size_t
+strlcpy(char *dst, const char *src, size_t size) {
+ char *d = dst;
+ const char *s = src;
+ size_t n = size;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0U && --n != 0U) {
+ do {
+ if ((*d++ = *s++) == 0) {
+ break;
+ }
+ } while (--n != 0U);
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0U) {
+ if (size != 0U) {
+ *d = '\0'; /* NUL-terminate dst */
+ }
+ while (*s++) {
+ }
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+#endif /* !defined(HAVE_STRLCPY) */
+
+#if !defined(HAVE_STRLCAT)
+size_t
+strlcat(char *dst, const char *src, size_t size) {
+ char *d = dst;
+ const char *s = src;
+ size_t n = size;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0U && *d != '\0') {
+ d++;
+ }
+ dlen = d - dst;
+ n = size - dlen;
+
+ if (n == 0U) {
+ return (dlen + strlen(s));
+ }
+ while (*s != '\0') {
+ if (n != 1U) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return (dlen + (s - src)); /* count does not include NUL */
+}
+#endif /* !defined(HAVE_STRLCAT) */
+
+#if !defined(HAVE_STRNSTR)
+char *
+strnstr(const char *s, const char *find, size_t slen) {
+ char c, sc;
+ size_t len;
+
+ if ((c = *find++) != '\0') {
+ len = strlen(find);
+ do {
+ do {
+ if (slen-- < 1 || (sc = *s++) == '\0')
+ return (NULL);
+ } while (sc != c);
+ if (len > slen)
+ return (NULL);
+ } while (strncmp(s, find, len) != 0);
+ s--;
+ }
+ return ((char *)s);
+}
+#endif
+
+int
+isc_string_strerror_r(int errnum, char *buf, size_t buflen) {
+ return (strerror_r(errnum, buf, buflen));
+}
diff --git a/lib/isc/symtab.c b/lib/isc/symtab.c
new file mode 100644
index 0000000..ff022a2
--- /dev/null
+++ b/lib/isc/symtab.c
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/string.h>
+#include <isc/symtab.h>
+#include <isc/util.h>
+
+typedef struct elt {
+ char *key;
+ unsigned int type;
+ isc_symvalue_t value;
+ LINK(struct elt) link;
+} elt_t;
+
+typedef LIST(elt_t) eltlist_t;
+
+#define SYMTAB_MAGIC ISC_MAGIC('S', 'y', 'm', 'T')
+#define VALID_SYMTAB(st) ISC_MAGIC_VALID(st, SYMTAB_MAGIC)
+
+struct isc_symtab {
+ /* Unlocked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ unsigned int size;
+ unsigned int count;
+ unsigned int maxload;
+ eltlist_t *table;
+ isc_symtabaction_t undefine_action;
+ void *undefine_arg;
+ bool case_sensitive;
+};
+
+isc_result_t
+isc_symtab_create(isc_mem_t *mctx, unsigned int size,
+ isc_symtabaction_t undefine_action, void *undefine_arg,
+ bool case_sensitive, isc_symtab_t **symtabp) {
+ isc_symtab_t *symtab;
+ unsigned int i;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(symtabp != NULL && *symtabp == NULL);
+ REQUIRE(size > 0); /* Should be prime. */
+
+ symtab = isc_mem_get(mctx, sizeof(*symtab));
+
+ symtab->mctx = NULL;
+ isc_mem_attach(mctx, &symtab->mctx);
+ symtab->table = isc_mem_get(mctx, size * sizeof(eltlist_t));
+ for (i = 0; i < size; i++) {
+ INIT_LIST(symtab->table[i]);
+ }
+ symtab->size = size;
+ symtab->count = 0;
+ symtab->maxload = size * 3 / 4;
+ symtab->undefine_action = undefine_action;
+ symtab->undefine_arg = undefine_arg;
+ symtab->case_sensitive = case_sensitive;
+ symtab->magic = SYMTAB_MAGIC;
+
+ *symtabp = symtab;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_symtab_destroy(isc_symtab_t **symtabp) {
+ isc_symtab_t *symtab;
+ unsigned int i;
+ elt_t *elt, *nelt;
+
+ REQUIRE(symtabp != NULL);
+ symtab = *symtabp;
+ *symtabp = NULL;
+ REQUIRE(VALID_SYMTAB(symtab));
+
+ for (i = 0; i < symtab->size; i++) {
+ for (elt = HEAD(symtab->table[i]); elt != NULL; elt = nelt) {
+ nelt = NEXT(elt, link);
+ if (symtab->undefine_action != NULL) {
+ (symtab->undefine_action)(elt->key, elt->type,
+ elt->value,
+ symtab->undefine_arg);
+ }
+ isc_mem_put(symtab->mctx, elt, sizeof(*elt));
+ }
+ }
+ isc_mem_put(symtab->mctx, symtab->table,
+ symtab->size * sizeof(eltlist_t));
+ symtab->magic = 0;
+ isc_mem_putanddetach(&symtab->mctx, symtab, sizeof(*symtab));
+}
+
+static unsigned int
+hash(const char *key, bool case_sensitive) {
+ const char *s;
+ unsigned int h = 0;
+ int c;
+
+ /*
+ * This hash function is similar to the one Ousterhout
+ * uses in Tcl.
+ */
+
+ if (case_sensitive) {
+ for (s = key; *s != '\0'; s++) {
+ h += (h << 3) + *s;
+ }
+ } else {
+ for (s = key; *s != '\0'; s++) {
+ c = *s;
+ c = tolower((unsigned char)c);
+ h += (h << 3) + c;
+ }
+ }
+
+ return (h);
+}
+
+#define FIND(s, k, t, b, e) \
+ b = hash((k), (s)->case_sensitive) % (s)->size; \
+ if ((s)->case_sensitive) { \
+ for (e = HEAD((s)->table[b]); e != NULL; e = NEXT(e, link)) { \
+ if (((t) == 0 || e->type == (t)) && \
+ strcmp(e->key, (k)) == 0) \
+ break; \
+ } \
+ } else { \
+ for (e = HEAD((s)->table[b]); e != NULL; e = NEXT(e, link)) { \
+ if (((t) == 0 || e->type == (t)) && \
+ strcasecmp(e->key, (k)) == 0) \
+ break; \
+ } \
+ }
+
+isc_result_t
+isc_symtab_lookup(isc_symtab_t *symtab, const char *key, unsigned int type,
+ isc_symvalue_t *value) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (elt == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (value != NULL) {
+ *value = elt->value;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+grow_table(isc_symtab_t *symtab) {
+ eltlist_t *newtable;
+ unsigned int i, newsize, newmax;
+
+ REQUIRE(symtab != NULL);
+
+ newsize = symtab->size * 2;
+ newmax = newsize * 3 / 4;
+ INSIST(newsize > 0U && newmax > 0U);
+
+ newtable = isc_mem_get(symtab->mctx, newsize * sizeof(eltlist_t));
+
+ for (i = 0; i < newsize; i++) {
+ INIT_LIST(newtable[i]);
+ }
+
+ for (i = 0; i < symtab->size; i++) {
+ elt_t *elt, *nelt;
+
+ for (elt = HEAD(symtab->table[i]); elt != NULL; elt = nelt) {
+ unsigned int hv;
+
+ nelt = NEXT(elt, link);
+
+ UNLINK(symtab->table[i], elt, link);
+ hv = hash(elt->key, symtab->case_sensitive);
+ APPEND(newtable[hv % newsize], elt, link);
+ }
+ }
+
+ isc_mem_put(symtab->mctx, symtab->table,
+ symtab->size * sizeof(eltlist_t));
+
+ symtab->table = newtable;
+ symtab->size = newsize;
+ symtab->maxload = newmax;
+}
+
+isc_result_t
+isc_symtab_define(isc_symtab_t *symtab, const char *key, unsigned int type,
+ isc_symvalue_t value, isc_symexists_t exists_policy) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+ REQUIRE(type != 0);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (exists_policy != isc_symexists_add && elt != NULL) {
+ if (exists_policy == isc_symexists_reject) {
+ return (ISC_R_EXISTS);
+ }
+ INSIST(exists_policy == isc_symexists_replace);
+ UNLINK(symtab->table[bucket], elt, link);
+ if (symtab->undefine_action != NULL) {
+ (symtab->undefine_action)(elt->key, elt->type,
+ elt->value,
+ symtab->undefine_arg);
+ }
+ } else {
+ elt = isc_mem_get(symtab->mctx, sizeof(*elt));
+ ISC_LINK_INIT(elt, link);
+ symtab->count++;
+ }
+
+ /*
+ * Though the "key" can be const coming in, it is not stored as const
+ * so that the calling program can easily have writable access to
+ * it in its undefine_action function. In the event that it *was*
+ * truly const coming in and then the caller modified it anyway ...
+ * well, don't do that!
+ */
+ DE_CONST(key, elt->key);
+ elt->type = type;
+ elt->value = value;
+
+ /*
+ * We prepend so that the most recent definition will be found.
+ */
+ PREPEND(symtab->table[bucket], elt, link);
+
+ if (symtab->count > symtab->maxload) {
+ grow_table(symtab);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_symtab_undefine(isc_symtab_t *symtab, const char *key, unsigned int type) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (elt == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (symtab->undefine_action != NULL) {
+ (symtab->undefine_action)(elt->key, elt->type, elt->value,
+ symtab->undefine_arg);
+ }
+ UNLINK(symtab->table[bucket], elt, link);
+ isc_mem_put(symtab->mctx, elt, sizeof(*elt));
+ symtab->count--;
+
+ return (ISC_R_SUCCESS);
+}
+
+unsigned int
+isc_symtab_count(isc_symtab_t *symtab) {
+ REQUIRE(VALID_SYMTAB(symtab));
+ return (symtab->count);
+}
diff --git a/lib/isc/syslog.c b/lib/isc/syslog.c
new file mode 100644
index 0000000..81b99ca
--- /dev/null
+++ b/lib/isc/syslog.c
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/syslog.h>
+#include <isc/util.h>
+
+static struct dsn_c_pvt_sfnt {
+ int val;
+ const char *strval;
+} facilities[] = { { LOG_KERN, "kern" },
+ { LOG_USER, "user" },
+ { LOG_MAIL, "mail" },
+ { LOG_DAEMON, "daemon" },
+ { LOG_AUTH, "auth" },
+ { LOG_SYSLOG, "syslog" },
+ { LOG_LPR, "lpr" },
+#ifdef LOG_NEWS
+ { LOG_NEWS, "news" },
+#endif /* ifdef LOG_NEWS */
+#ifdef LOG_UUCP
+ { LOG_UUCP, "uucp" },
+#endif /* ifdef LOG_UUCP */
+#ifdef LOG_CRON
+ { LOG_CRON, "cron" },
+#endif /* ifdef LOG_CRON */
+#ifdef LOG_AUTHPRIV
+ { LOG_AUTHPRIV, "authpriv" },
+#endif /* ifdef LOG_AUTHPRIV */
+#ifdef LOG_FTP
+ { LOG_FTP, "ftp" },
+#endif /* ifdef LOG_FTP */
+ { LOG_LOCAL0, "local0" },
+ { LOG_LOCAL1, "local1" },
+ { LOG_LOCAL2, "local2" },
+ { LOG_LOCAL3, "local3" },
+ { LOG_LOCAL4, "local4" },
+ { LOG_LOCAL5, "local5" },
+ { LOG_LOCAL6, "local6" },
+ { LOG_LOCAL7, "local7" },
+ { 0, NULL } };
+
+isc_result_t
+isc_syslog_facilityfromstring(const char *str, int *facilityp) {
+ int i;
+
+ REQUIRE(str != NULL);
+ REQUIRE(facilityp != NULL);
+
+ for (i = 0; facilities[i].strval != NULL; i++) {
+ if (strcasecmp(facilities[i].strval, str) == 0) {
+ *facilityp = facilities[i].val;
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+}
diff --git a/lib/isc/task.c b/lib/isc/task.c
new file mode 100644
index 0000000..439d430
--- /dev/null
+++ b/lib/isc/task.c
@@ -0,0 +1,1368 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 */
+
+/*
+ * XXXRTH Need to document the states a task can be in, and the rules
+ * for changing states.
+ */
+
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/atomic.h>
+#include <isc/condition.h>
+#include <isc/event.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#ifdef HAVE_LIBXML2
+#include <libxml/xmlwriter.h>
+#define ISC_XMLCHAR (const xmlChar *)
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#include <json_object.h>
+#endif /* HAVE_JSON_C */
+
+#include "task_p.h"
+
+/*
+ * Task manager is built around 'as little locking as possible' concept.
+ * Each thread has his own queue of tasks to be run, if a task is in running
+ * state it will stay on the runner it's currently on, if a task is in idle
+ * state it can be woken up on a specific runner with isc_task_sendto - that
+ * helps with data locality on CPU.
+ *
+ * To make load even some tasks (from task pools) are bound to specific
+ * queues using isc_task_create_bound. This way load balancing between
+ * CPUs/queues happens on the higher layer.
+ */
+
+#ifdef ISC_TASK_TRACE
+#define XTRACE(m) \
+ fprintf(stderr, "task %p thread %zu: %s\n", task, isc_tid_v, (m))
+#define XTTRACE(t, m) \
+ fprintf(stderr, "task %p thread %zu: %s\n", (t), isc_tid_v, (m))
+#define XTHREADTRACE(m) fprintf(stderr, "thread %zu: %s\n", isc_tid_v, (m))
+#else /* ifdef ISC_TASK_TRACE */
+#define XTRACE(m)
+#define XTTRACE(t, m)
+#define XTHREADTRACE(m)
+#endif /* ifdef ISC_TASK_TRACE */
+
+/***
+ *** Types.
+ ***/
+
+typedef enum {
+ task_state_idle, /* not doing anything, events queue empty */
+ task_state_ready, /* waiting in worker's queue */
+ task_state_running, /* actively processing events */
+ task_state_done /* shutting down, no events or references */
+} task_state_t;
+
+#if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C)
+static const char *statenames[] = {
+ "idle",
+ "ready",
+ "running",
+ "done",
+};
+#endif /* if defined(HAVE_LIBXML2) || defined(HAVE_JSON_C) */
+
+#define TASK_MAGIC ISC_MAGIC('T', 'A', 'S', 'K')
+#define VALID_TASK(t) ISC_MAGIC_VALID(t, TASK_MAGIC)
+
+struct isc_task {
+ /* Not locked. */
+ unsigned int magic;
+ isc_taskmgr_t *manager;
+ isc_mutex_t lock;
+ /* Locked by task lock. */
+ int threadid;
+ task_state_t state;
+ isc_refcount_t references;
+ isc_refcount_t running;
+ isc_eventlist_t events;
+ isc_eventlist_t on_shutdown;
+ unsigned int nevents;
+ unsigned int quantum;
+ isc_stdtime_t now;
+ isc_time_t tnow;
+ char name[16];
+ void *tag;
+ bool bound;
+ /* Protected by atomics */
+ atomic_bool shuttingdown;
+ atomic_bool privileged;
+ /* Locked by task manager lock. */
+ LINK(isc_task_t) link;
+};
+
+#define TASK_SHUTTINGDOWN(t) (atomic_load_acquire(&(t)->shuttingdown))
+#define TASK_PRIVILEGED(t) (atomic_load_acquire(&(t)->privileged))
+
+#define TASK_MANAGER_MAGIC ISC_MAGIC('T', 'S', 'K', 'M')
+#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TASK_MANAGER_MAGIC)
+
+struct isc_taskmgr {
+ /* Not locked. */
+ unsigned int magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ atomic_uint_fast32_t tasks_count;
+ isc_nm_t *netmgr;
+
+ /* Locked by task manager lock. */
+ unsigned int default_quantum;
+ LIST(isc_task_t) tasks;
+ atomic_uint_fast32_t mode;
+ atomic_bool exclusive_req;
+ bool exiting;
+ isc_task_t *excl;
+};
+
+#define DEFAULT_DEFAULT_QUANTUM 25
+
+/*%
+ * The following are intended for internal use (indicated by "isc__"
+ * prefix) but are not declared as static, allowing direct access from
+ * unit tests etc.
+ */
+
+bool
+isc_task_purgeevent(isc_task_t *task, isc_event_t *event);
+void
+isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task);
+isc_result_t
+isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp);
+
+/***
+ *** Tasks.
+ ***/
+
+static void
+task_finished(isc_task_t *task) {
+ isc_taskmgr_t *manager = task->manager;
+ isc_mem_t *mctx = manager->mctx;
+ REQUIRE(EMPTY(task->events));
+ REQUIRE(task->nevents == 0);
+ REQUIRE(EMPTY(task->on_shutdown));
+ REQUIRE(task->state == task_state_done);
+
+ XTRACE("task_finished");
+
+ isc_refcount_destroy(&task->running);
+ isc_refcount_destroy(&task->references);
+
+ LOCK(&manager->lock);
+ UNLINK(manager->tasks, task, link);
+ atomic_fetch_sub(&manager->tasks_count, 1);
+ UNLOCK(&manager->lock);
+
+ isc_mutex_destroy(&task->lock);
+ task->magic = 0;
+ isc_mem_put(mctx, task, sizeof(*task));
+
+ isc_taskmgr_detach(&manager);
+}
+
+isc_result_t
+isc_task_create(isc_taskmgr_t *manager, unsigned int quantum,
+ isc_task_t **taskp) {
+ return (isc_task_create_bound(manager, quantum, taskp, -1));
+}
+
+isc_result_t
+isc_task_create_bound(isc_taskmgr_t *manager, unsigned int quantum,
+ isc_task_t **taskp, int threadid) {
+ isc_task_t *task = NULL;
+ bool exiting;
+
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(taskp != NULL && *taskp == NULL);
+
+ XTRACE("isc_task_create");
+
+ task = isc_mem_get(manager->mctx, sizeof(*task));
+ *task = (isc_task_t){ 0 };
+
+ isc_taskmgr_attach(manager, &task->manager);
+
+ if (threadid == -1) {
+ /*
+ * Task is not pinned to a queue, it's threadid will be
+ * chosen when first task will be sent to it - either
+ * randomly or specified by isc_task_sendto.
+ */
+ task->bound = false;
+ task->threadid = -1;
+ } else {
+ /*
+ * Task is pinned to a queue, it'll always be run
+ * by a specific thread.
+ */
+ task->bound = true;
+ task->threadid = threadid;
+ }
+
+ isc_mutex_init(&task->lock);
+ task->state = task_state_idle;
+
+ isc_refcount_init(&task->references, 1);
+ isc_refcount_init(&task->running, 0);
+ INIT_LIST(task->events);
+ INIT_LIST(task->on_shutdown);
+ task->nevents = 0;
+ task->quantum = (quantum > 0) ? quantum : manager->default_quantum;
+ atomic_init(&task->shuttingdown, false);
+ atomic_init(&task->privileged, false);
+ task->now = 0;
+ isc_time_settoepoch(&task->tnow);
+ memset(task->name, 0, sizeof(task->name));
+ task->tag = NULL;
+ INIT_LINK(task, link);
+ task->magic = TASK_MAGIC;
+
+ LOCK(&manager->lock);
+ exiting = manager->exiting;
+ if (!exiting) {
+ APPEND(manager->tasks, task, link);
+ atomic_fetch_add(&manager->tasks_count, 1);
+ }
+ UNLOCK(&manager->lock);
+
+ if (exiting) {
+ isc_refcount_destroy(&task->running);
+ isc_refcount_decrement(&task->references);
+ isc_refcount_destroy(&task->references);
+ isc_mutex_destroy(&task->lock);
+ isc_taskmgr_detach(&task->manager);
+ isc_mem_put(manager->mctx, task, sizeof(*task));
+ return (ISC_R_SHUTTINGDOWN);
+ }
+
+ *taskp = task;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_task_attach(isc_task_t *source, isc_task_t **targetp) {
+ /*
+ * Attach *targetp to source.
+ */
+
+ REQUIRE(VALID_TASK(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ XTTRACE(source, "isc_task_attach");
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static bool
+task_shutdown(isc_task_t *task) {
+ bool was_idle = false;
+ isc_event_t *event, *prev;
+
+ /*
+ * Caller must be holding the task's lock.
+ */
+
+ XTRACE("task_shutdown");
+
+ if (atomic_compare_exchange_strong(&task->shuttingdown,
+ &(bool){ false }, true))
+ {
+ XTRACE("shutting down");
+ if (task->state == task_state_idle) {
+ INSIST(EMPTY(task->events));
+ task->state = task_state_ready;
+ was_idle = true;
+ }
+ INSIST(task->state == task_state_ready ||
+ task->state == task_state_running);
+
+ /*
+ * Note that we post shutdown events LIFO.
+ */
+ for (event = TAIL(task->on_shutdown); event != NULL;
+ event = prev)
+ {
+ prev = PREV(event, ev_link);
+ DEQUEUE(task->on_shutdown, event, ev_link);
+ ENQUEUE(task->events, event, ev_link);
+ task->nevents++;
+ }
+ }
+
+ return (was_idle);
+}
+
+/*
+ * Moves a task onto the appropriate run queue.
+ *
+ * Caller must NOT hold queue lock.
+ */
+static void
+task_ready(isc_task_t *task) {
+ isc_taskmgr_t *manager = task->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ XTRACE("task_ready");
+
+ isc_refcount_increment0(&task->running);
+ LOCK(&task->lock);
+ isc_nm_task_enqueue(manager->netmgr, task, task->threadid);
+ UNLOCK(&task->lock);
+}
+
+void
+isc_task_ready(isc_task_t *task) {
+ task_ready(task);
+}
+
+static bool
+task_detach(isc_task_t *task) {
+ /*
+ * Caller must be holding the task lock.
+ */
+
+ XTRACE("detach");
+
+ if (isc_refcount_decrement(&task->references) == 1 &&
+ task->state == task_state_idle)
+ {
+ INSIST(EMPTY(task->events));
+ /*
+ * There are no references to this task, and no
+ * pending events. We could try to optimize and
+ * either initiate shutdown or clean up the task,
+ * depending on its state, but it's easier to just
+ * make the task ready and allow run() or the event
+ * loop to deal with shutting down and termination.
+ */
+ task->state = task_state_ready;
+ return (true);
+ }
+
+ return (false);
+}
+
+void
+isc_task_detach(isc_task_t **taskp) {
+ isc_task_t *task;
+ bool was_idle;
+
+ /*
+ * Detach *taskp from its task.
+ */
+
+ REQUIRE(taskp != NULL);
+ task = *taskp;
+ REQUIRE(VALID_TASK(task));
+
+ XTRACE("isc_task_detach");
+
+ LOCK(&task->lock);
+ was_idle = task_detach(task);
+ UNLOCK(&task->lock);
+
+ if (was_idle) {
+ task_ready(task);
+ }
+
+ *taskp = NULL;
+}
+
+static bool
+task_send(isc_task_t *task, isc_event_t **eventp, int c) {
+ bool was_idle = false;
+ isc_event_t *event;
+
+ /*
+ * Caller must be holding the task lock.
+ */
+
+ REQUIRE(eventp != NULL);
+ event = *eventp;
+ *eventp = NULL;
+ REQUIRE(event != NULL);
+ REQUIRE(event->ev_type > 0);
+ REQUIRE(task->state != task_state_done);
+ REQUIRE(!ISC_LINK_LINKED(event, ev_ratelink));
+
+ XTRACE("task_send");
+
+ if (task->bound) {
+ c = task->threadid;
+ } else if (c < 0) {
+ c = -1;
+ }
+
+ if (task->state == task_state_idle) {
+ was_idle = true;
+ task->threadid = c;
+ INSIST(EMPTY(task->events));
+ task->state = task_state_ready;
+ }
+ INSIST(task->state == task_state_ready ||
+ task->state == task_state_running);
+ ENQUEUE(task->events, event, ev_link);
+ task->nevents++;
+
+ return (was_idle);
+}
+
+void
+isc_task_send(isc_task_t *task, isc_event_t **eventp) {
+ isc_task_sendto(task, eventp, -1);
+}
+
+void
+isc_task_sendanddetach(isc_task_t **taskp, isc_event_t **eventp) {
+ isc_task_sendtoanddetach(taskp, eventp, -1);
+}
+
+void
+isc_task_sendto(isc_task_t *task, isc_event_t **eventp, int c) {
+ bool was_idle;
+
+ /*
+ * Send '*event' to 'task'.
+ */
+
+ REQUIRE(VALID_TASK(task));
+ XTRACE("isc_task_send");
+
+ /*
+ * We're trying hard to hold locks for as short a time as possible.
+ * We're also trying to hold as few locks as possible. This is why
+ * some processing is deferred until after the lock is released.
+ */
+ LOCK(&task->lock);
+ was_idle = task_send(task, eventp, c);
+ UNLOCK(&task->lock);
+
+ if (was_idle) {
+ /*
+ * We need to add this task to the ready queue.
+ *
+ * We've waited until now to do it because making a task
+ * ready requires locking the manager. If we tried to do
+ * this while holding the task lock, we could deadlock.
+ *
+ * We've changed the state to ready, so no one else will
+ * be trying to add this task to the ready queue. The
+ * only way to leave the ready state is by executing the
+ * task. It thus doesn't matter if events are added,
+ * removed, or a shutdown is started in the interval
+ * between the time we released the task lock, and the time
+ * we add the task to the ready queue.
+ */
+ task_ready(task);
+ }
+}
+
+void
+isc_task_sendtoanddetach(isc_task_t **taskp, isc_event_t **eventp, int c) {
+ bool idle1, idle2;
+ isc_task_t *task;
+
+ /*
+ * Send '*event' to '*taskp' and then detach '*taskp' from its
+ * task.
+ */
+
+ REQUIRE(taskp != NULL);
+ task = *taskp;
+ REQUIRE(VALID_TASK(task));
+ XTRACE("isc_task_sendanddetach");
+
+ LOCK(&task->lock);
+ idle1 = task_send(task, eventp, c);
+ idle2 = task_detach(task);
+ UNLOCK(&task->lock);
+
+ /*
+ * If idle1, then idle2 shouldn't be true as well since we're holding
+ * the task lock, and thus the task cannot switch from ready back to
+ * idle.
+ */
+ INSIST(!(idle1 && idle2));
+
+ if (idle1 || idle2) {
+ task_ready(task);
+ }
+
+ *taskp = NULL;
+}
+
+#define PURGE_OK(event) (((event)->ev_attributes & ISC_EVENTATTR_NOPURGE) == 0)
+
+static unsigned int
+dequeue_events(isc_task_t *task, void *sender, isc_eventtype_t first,
+ isc_eventtype_t last, void *tag, isc_eventlist_t *events,
+ bool purging) {
+ isc_event_t *event, *next_event;
+ unsigned int count = 0;
+
+ REQUIRE(VALID_TASK(task));
+ REQUIRE(last >= first);
+
+ XTRACE("dequeue_events");
+
+ /*
+ * Events matching 'sender', whose type is >= first and <= last, and
+ * whose tag is 'tag' will be dequeued. If 'purging', matching events
+ * which are marked as unpurgable will not be dequeued.
+ *
+ * sender == NULL means "any sender", and tag == NULL means "any tag".
+ */
+
+ LOCK(&task->lock);
+
+ for (event = HEAD(task->events); event != NULL; event = next_event) {
+ next_event = NEXT(event, ev_link);
+ if (event->ev_type >= first && event->ev_type <= last &&
+ (sender == NULL || event->ev_sender == sender) &&
+ (tag == NULL || event->ev_tag == tag) &&
+ (!purging || PURGE_OK(event)))
+ {
+ DEQUEUE(task->events, event, ev_link);
+ task->nevents--;
+ ENQUEUE(*events, event, ev_link);
+ count++;
+ }
+ }
+
+ UNLOCK(&task->lock);
+
+ return (count);
+}
+
+unsigned int
+isc_task_purgerange(isc_task_t *task, void *sender, isc_eventtype_t first,
+ isc_eventtype_t last, void *tag) {
+ unsigned int count;
+ isc_eventlist_t events;
+ isc_event_t *event, *next_event;
+ REQUIRE(VALID_TASK(task));
+
+ /*
+ * Purge events from a task's event queue.
+ */
+
+ XTRACE("isc_task_purgerange");
+
+ ISC_LIST_INIT(events);
+
+ count = dequeue_events(task, sender, first, last, tag, &events, true);
+
+ for (event = HEAD(events); event != NULL; event = next_event) {
+ next_event = NEXT(event, ev_link);
+ ISC_LIST_UNLINK(events, event, ev_link);
+ isc_event_free(&event);
+ }
+
+ /*
+ * Note that purging never changes the state of the task.
+ */
+
+ return (count);
+}
+
+unsigned int
+isc_task_purge(isc_task_t *task, void *sender, isc_eventtype_t type,
+ void *tag) {
+ /*
+ * Purge events from a task's event queue.
+ */
+ REQUIRE(VALID_TASK(task));
+
+ XTRACE("isc_task_purge");
+
+ return (isc_task_purgerange(task, sender, type, type, tag));
+}
+
+bool
+isc_task_purgeevent(isc_task_t *task, isc_event_t *event) {
+ bool found = false;
+
+ /*
+ * Purge 'event' from a task's event queue.
+ */
+
+ REQUIRE(VALID_TASK(task));
+
+ /*
+ * If 'event' is on the task's event queue, it will be purged,
+ * unless it is marked as unpurgeable. 'event' does not have to be
+ * on the task's event queue; in fact, it can even be an invalid
+ * pointer. Purging only occurs if the event is actually on the task's
+ * event queue.
+ *
+ * Purging never changes the state of the task.
+ */
+
+ LOCK(&task->lock);
+ if (ISC_LINK_LINKED(event, ev_link)) {
+ DEQUEUE(task->events, event, ev_link);
+ task->nevents--;
+ found = true;
+ }
+ UNLOCK(&task->lock);
+
+ if (!found) {
+ return (false);
+ }
+
+ isc_event_free(&event);
+
+ return (true);
+}
+
+unsigned int
+isc_task_unsend(isc_task_t *task, void *sender, isc_eventtype_t type, void *tag,
+ isc_eventlist_t *events) {
+ /*
+ * Remove events from a task's event queue.
+ */
+
+ XTRACE("isc_task_unsend");
+
+ return (dequeue_events(task, sender, type, type, tag, events, false));
+}
+
+isc_result_t
+isc_task_onshutdown(isc_task_t *task, isc_taskaction_t action, void *arg) {
+ bool disallowed = false;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_event_t *event;
+
+ /*
+ * Send a shutdown event with action 'action' and argument 'arg' when
+ * 'task' is shutdown.
+ */
+
+ REQUIRE(VALID_TASK(task));
+ REQUIRE(action != NULL);
+
+ event = isc_event_allocate(task->manager->mctx, NULL,
+ ISC_TASKEVENT_SHUTDOWN, action, arg,
+ sizeof(*event));
+
+ if (TASK_SHUTTINGDOWN(task)) {
+ disallowed = true;
+ result = ISC_R_SHUTTINGDOWN;
+ } else {
+ LOCK(&task->lock);
+ ENQUEUE(task->on_shutdown, event, ev_link);
+ UNLOCK(&task->lock);
+ }
+
+ if (disallowed) {
+ isc_mem_put(task->manager->mctx, event, sizeof(*event));
+ }
+
+ return (result);
+}
+
+void
+isc_task_shutdown(isc_task_t *task) {
+ bool was_idle;
+
+ /*
+ * Shutdown 'task'.
+ */
+
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ was_idle = task_shutdown(task);
+ UNLOCK(&task->lock);
+
+ if (was_idle) {
+ task_ready(task);
+ }
+}
+
+void
+isc_task_destroy(isc_task_t **taskp) {
+ /*
+ * Destroy '*taskp'.
+ */
+
+ REQUIRE(taskp != NULL);
+
+ isc_task_shutdown(*taskp);
+ isc_task_detach(taskp);
+}
+
+void
+isc_task_setname(isc_task_t *task, const char *name, void *tag) {
+ /*
+ * Name 'task'.
+ */
+
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ strlcpy(task->name, name, sizeof(task->name));
+ task->tag = tag;
+ UNLOCK(&task->lock);
+}
+
+const char *
+isc_task_getname(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (task->name);
+}
+
+void *
+isc_task_gettag(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (task->tag);
+}
+
+isc_nm_t *
+isc_task_getnetmgr(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (task->manager->netmgr);
+}
+
+void
+isc_task_setquantum(isc_task_t *task, unsigned int quantum) {
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ task->quantum = (quantum > 0) ? quantum
+ : task->manager->default_quantum;
+ UNLOCK(&task->lock);
+}
+
+/***
+ *** Task Manager.
+ ***/
+
+static isc_result_t
+task_run(isc_task_t *task) {
+ unsigned int dispatch_count = 0;
+ bool finished = false;
+ isc_event_t *event = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ uint32_t quantum;
+
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ quantum = task->quantum;
+
+ if (task->state != task_state_ready) {
+ goto done;
+ }
+
+ INSIST(task->state == task_state_ready);
+ task->state = task_state_running;
+ XTRACE("running");
+ XTRACE(task->name);
+ TIME_NOW(&task->tnow);
+ task->now = isc_time_seconds(&task->tnow);
+
+ while (true) {
+ if (!EMPTY(task->events)) {
+ event = HEAD(task->events);
+ DEQUEUE(task->events, event, ev_link);
+ task->nevents--;
+
+ /*
+ * Execute the event action.
+ */
+ XTRACE("execute action");
+ XTRACE(task->name);
+ if (event->ev_action != NULL) {
+ UNLOCK(&task->lock);
+ (event->ev_action)(task, event);
+ LOCK(&task->lock);
+ }
+ XTRACE("execution complete");
+ dispatch_count++;
+ }
+
+ if (isc_refcount_current(&task->references) == 0 &&
+ EMPTY(task->events) && !TASK_SHUTTINGDOWN(task))
+ {
+ /*
+ * There are no references and no pending events for
+ * this task, which means it will not become runnable
+ * again via an external action (such as sending an
+ * event or detaching).
+ *
+ * We initiate shutdown to prevent it from becoming a
+ * zombie.
+ *
+ * We do this here instead of in the "if
+ * EMPTY(task->events)" block below because:
+ *
+ * If we post no shutdown events, we want the task
+ * to finish.
+ *
+ * If we did post shutdown events, will still want
+ * the task's quantum to be applied.
+ */
+ INSIST(!task_shutdown(task));
+ }
+
+ if (EMPTY(task->events)) {
+ /*
+ * Nothing else to do for this task right now.
+ */
+ XTRACE("empty");
+ if (isc_refcount_current(&task->references) == 0 &&
+ TASK_SHUTTINGDOWN(task))
+ {
+ /*
+ * The task is done.
+ */
+ XTRACE("done");
+ task->state = task_state_done;
+ } else if (task->state == task_state_running) {
+ XTRACE("idling");
+ task->state = task_state_idle;
+ }
+ break;
+ } else if (dispatch_count >= quantum) {
+ /*
+ * Our quantum has expired, but there is more work to be
+ * done. We'll requeue it to the ready queue later.
+ *
+ * We don't check quantum until dispatching at least one
+ * event, so the minimum quantum is one.
+ */
+ XTRACE("quantum");
+ task->state = task_state_ready;
+ result = ISC_R_QUOTA;
+ break;
+ }
+ }
+
+done:
+ if (isc_refcount_decrement(&task->running) == 1 &&
+ task->state == task_state_done)
+ {
+ finished = true;
+ }
+ UNLOCK(&task->lock);
+
+ if (finished) {
+ task_finished(task);
+ }
+
+ return (result);
+}
+
+isc_result_t
+isc_task_run(isc_task_t *task) {
+ return (task_run(task));
+}
+
+static void
+manager_free(isc_taskmgr_t *manager) {
+ isc_refcount_destroy(&manager->references);
+ isc_nm_detach(&manager->netmgr);
+
+ isc_mutex_destroy(&manager->lock);
+ manager->magic = 0;
+ isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
+}
+
+void
+isc_taskmgr_attach(isc_taskmgr_t *source, isc_taskmgr_t **targetp) {
+ REQUIRE(VALID_MANAGER(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+void
+isc_taskmgr_detach(isc_taskmgr_t **managerp) {
+ REQUIRE(managerp != NULL);
+ REQUIRE(VALID_MANAGER(*managerp));
+
+ isc_taskmgr_t *manager = *managerp;
+ *managerp = NULL;
+
+ if (isc_refcount_decrement(&manager->references) == 1) {
+ manager_free(manager);
+ }
+}
+
+isc_result_t
+isc__taskmgr_create(isc_mem_t *mctx, unsigned int default_quantum, isc_nm_t *nm,
+ isc_taskmgr_t **managerp) {
+ isc_taskmgr_t *manager;
+
+ /*
+ * Create a new task manager.
+ */
+
+ REQUIRE(managerp != NULL && *managerp == NULL);
+ REQUIRE(nm != NULL);
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+ *manager = (isc_taskmgr_t){ .magic = TASK_MANAGER_MAGIC };
+
+ isc_mutex_init(&manager->lock);
+
+ if (default_quantum == 0) {
+ default_quantum = DEFAULT_DEFAULT_QUANTUM;
+ }
+ manager->default_quantum = default_quantum;
+
+ if (nm != NULL) {
+ isc_nm_attach(nm, &manager->netmgr);
+ }
+
+ INIT_LIST(manager->tasks);
+ atomic_init(&manager->mode, isc_taskmgrmode_normal);
+ atomic_init(&manager->exclusive_req, false);
+ atomic_init(&manager->tasks_count, 0);
+
+ isc_mem_attach(mctx, &manager->mctx);
+
+ isc_refcount_init(&manager->references, 1);
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc__taskmgr_shutdown(isc_taskmgr_t *manager) {
+ isc_task_t *task;
+
+ REQUIRE(VALID_MANAGER(manager));
+
+ XTHREADTRACE("isc_taskmgr_shutdown");
+ /*
+ * Only one non-worker thread may ever call this routine.
+ * If a worker thread wants to initiate shutdown of the
+ * task manager, it should ask some non-worker thread to call
+ * isc_taskmgr_destroy(), e.g. by signalling a condition variable
+ * that the startup thread is sleeping on.
+ */
+
+ /*
+ * Unlike elsewhere, we're going to hold this lock a long time.
+ * We need to do so, because otherwise the list of tasks could
+ * change while we were traversing it.
+ *
+ * This is also the only function where we will hold both the
+ * task manager lock and a task lock at the same time.
+ */
+ LOCK(&manager->lock);
+ if (manager->excl != NULL) {
+ isc_task_detach((isc_task_t **)&manager->excl);
+ }
+
+ /*
+ * Make sure we only get called once.
+ */
+ INSIST(manager->exiting == false);
+ manager->exiting = true;
+
+ /*
+ * Post shutdown event(s) to every task (if they haven't already been
+ * posted).
+ */
+ for (task = HEAD(manager->tasks); task != NULL; task = NEXT(task, link))
+ {
+ bool was_idle;
+
+ LOCK(&task->lock);
+ was_idle = task_shutdown(task);
+ UNLOCK(&task->lock);
+
+ if (was_idle) {
+ task_ready(task);
+ }
+ }
+
+ UNLOCK(&manager->lock);
+}
+
+void
+isc__taskmgr_destroy(isc_taskmgr_t **managerp) {
+ REQUIRE(managerp != NULL && VALID_MANAGER(*managerp));
+ XTHREADTRACE("isc_taskmgr_destroy");
+
+#ifdef ISC_TASK_TRACE
+ int counter = 0;
+ while (isc_refcount_current(&(*managerp)->references) > 1 &&
+ counter++ < 1000)
+ {
+ usleep(10 * 1000);
+ }
+ INSIST(counter < 1000);
+#else
+ while (isc_refcount_current(&(*managerp)->references) > 1) {
+ usleep(10 * 1000);
+ }
+#endif
+
+ isc_taskmgr_detach(managerp);
+}
+
+void
+isc_taskmgr_setexcltask(isc_taskmgr_t *mgr, isc_task_t *task) {
+ REQUIRE(VALID_MANAGER(mgr));
+ REQUIRE(VALID_TASK(task));
+
+ LOCK(&task->lock);
+ REQUIRE(task->threadid == 0);
+ UNLOCK(&task->lock);
+
+ LOCK(&mgr->lock);
+ if (mgr->excl != NULL) {
+ isc_task_detach(&mgr->excl);
+ }
+ isc_task_attach(task, &mgr->excl);
+ UNLOCK(&mgr->lock);
+}
+
+isc_result_t
+isc_taskmgr_excltask(isc_taskmgr_t *mgr, isc_task_t **taskp) {
+ isc_result_t result;
+
+ REQUIRE(VALID_MANAGER(mgr));
+ REQUIRE(taskp != NULL && *taskp == NULL);
+
+ LOCK(&mgr->lock);
+ if (mgr->excl != NULL) {
+ isc_task_attach(mgr->excl, taskp);
+ result = ISC_R_SUCCESS;
+ } else if (mgr->exiting) {
+ result = ISC_R_SHUTTINGDOWN;
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+
+isc_result_t
+isc_task_beginexclusive(isc_task_t *task) {
+ isc_taskmgr_t *manager;
+
+ REQUIRE(VALID_TASK(task));
+
+ manager = task->manager;
+
+ REQUIRE(task->state == task_state_running);
+
+ LOCK(&manager->lock);
+ REQUIRE(task == manager->excl ||
+ (manager->exiting && manager->excl == NULL));
+ UNLOCK(&manager->lock);
+
+ if (!atomic_compare_exchange_strong(&manager->exclusive_req,
+ &(bool){ false }, true))
+ {
+ return (ISC_R_LOCKBUSY);
+ }
+
+ if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1),
+ "exclusive task mode: %s", "starting");
+ }
+
+ isc_nm_pause(manager->netmgr);
+
+ if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1),
+ "exclusive task mode: %s", "started");
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_task_endexclusive(isc_task_t *task) {
+ isc_taskmgr_t *manager = NULL;
+
+ REQUIRE(VALID_TASK(task));
+ REQUIRE(task->state == task_state_running);
+
+ manager = task->manager;
+
+ if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1),
+ "exclusive task mode: %s", "ending");
+ }
+
+ isc_nm_resume(manager->netmgr);
+
+ if (isc_log_wouldlog(isc_lctx, ISC_LOG_DEBUG(1))) {
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
+ ISC_LOGMODULE_OTHER, ISC_LOG_DEBUG(1),
+ "exclusive task mode: %s", "ended");
+ }
+
+ atomic_compare_exchange_enforced(&manager->exclusive_req,
+ &(bool){ true }, false);
+}
+
+void
+isc_taskmgr_setmode(isc_taskmgr_t *manager, isc_taskmgrmode_t mode) {
+ atomic_store(&manager->mode, mode);
+}
+
+isc_taskmgrmode_t
+isc_taskmgr_mode(isc_taskmgr_t *manager) {
+ return (atomic_load(&manager->mode));
+}
+
+void
+isc_task_setprivilege(isc_task_t *task, bool priv) {
+ REQUIRE(VALID_TASK(task));
+
+ atomic_store_release(&task->privileged, priv);
+}
+
+bool
+isc_task_getprivilege(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (TASK_PRIVILEGED(task));
+}
+
+bool
+isc_task_privileged(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (isc_taskmgr_mode(task->manager) && TASK_PRIVILEGED(task));
+}
+
+bool
+isc_task_exiting(isc_task_t *task) {
+ REQUIRE(VALID_TASK(task));
+
+ return (TASK_SHUTTINGDOWN(task));
+}
+
+#ifdef HAVE_LIBXML2
+#define TRY0(a) \
+ do { \
+ xmlrc = (a); \
+ if (xmlrc < 0) \
+ goto error; \
+ } while (0)
+int
+isc_taskmgr_renderxml(isc_taskmgr_t *mgr, void *writer0) {
+ isc_task_t *task = NULL;
+ int xmlrc;
+ xmlTextWriterPtr writer = (xmlTextWriterPtr)writer0;
+
+ LOCK(&mgr->lock);
+
+ /*
+ * Write out the thread-model, and some details about each depending
+ * on which type is enabled.
+ */
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "thread-model"));
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "type"));
+ TRY0(xmlTextWriterWriteString(writer, ISC_XMLCHAR "threaded"));
+ TRY0(xmlTextWriterEndElement(writer)); /* type */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "default-quantum"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%d",
+ mgr->default_quantum));
+ TRY0(xmlTextWriterEndElement(writer)); /* default-quantum */
+
+ TRY0(xmlTextWriterEndElement(writer)); /* thread-model */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "tasks"));
+ task = ISC_LIST_HEAD(mgr->tasks);
+ while (task != NULL) {
+ LOCK(&task->lock);
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "task"));
+
+ if (task->name[0] != 0) {
+ TRY0(xmlTextWriterStartElement(writer,
+ ISC_XMLCHAR "name"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s",
+ task->name));
+ TRY0(xmlTextWriterEndElement(writer)); /* name */
+ }
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "reference"
+ "s"));
+ TRY0(xmlTextWriterWriteFormatString(
+ writer, "%" PRIuFAST32,
+ isc_refcount_current(&task->references)));
+ TRY0(xmlTextWriterEndElement(writer)); /* references */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "id"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%p", task));
+ TRY0(xmlTextWriterEndElement(writer)); /* id */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "state"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%s",
+ statenames[task->state]));
+ TRY0(xmlTextWriterEndElement(writer)); /* state */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "quantum"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%d",
+ task->quantum));
+ TRY0(xmlTextWriterEndElement(writer)); /* quantum */
+
+ TRY0(xmlTextWriterStartElement(writer, ISC_XMLCHAR "events"));
+ TRY0(xmlTextWriterWriteFormatString(writer, "%d",
+ task->nevents));
+ TRY0(xmlTextWriterEndElement(writer)); /* events */
+
+ TRY0(xmlTextWriterEndElement(writer));
+
+ UNLOCK(&task->lock);
+ task = ISC_LIST_NEXT(task, link);
+ }
+ TRY0(xmlTextWriterEndElement(writer)); /* tasks */
+
+error:
+ if (task != NULL) {
+ UNLOCK(&task->lock);
+ }
+ UNLOCK(&mgr->lock);
+
+ return (xmlrc);
+}
+#endif /* HAVE_LIBXML2 */
+
+#ifdef HAVE_JSON_C
+#define CHECKMEM(m) \
+ do { \
+ if (m == NULL) { \
+ result = ISC_R_NOMEMORY; \
+ goto error; \
+ } \
+ } while (0)
+
+isc_result_t
+isc_taskmgr_renderjson(isc_taskmgr_t *mgr, void *tasks0) {
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_task_t *task = NULL;
+ json_object *obj = NULL, *array = NULL, *taskobj = NULL;
+ json_object *tasks = (json_object *)tasks0;
+
+ LOCK(&mgr->lock);
+
+ /*
+ * Write out the thread-model, and some details about each depending
+ * on which type is enabled.
+ */
+ obj = json_object_new_string("threaded");
+ CHECKMEM(obj);
+ json_object_object_add(tasks, "thread-model", obj);
+
+ obj = json_object_new_int(mgr->default_quantum);
+ CHECKMEM(obj);
+ json_object_object_add(tasks, "default-quantum", obj);
+
+ array = json_object_new_array();
+ CHECKMEM(array);
+
+ for (task = ISC_LIST_HEAD(mgr->tasks); task != NULL;
+ task = ISC_LIST_NEXT(task, link))
+ {
+ char buf[255];
+
+ LOCK(&task->lock);
+
+ taskobj = json_object_new_object();
+ CHECKMEM(taskobj);
+ json_object_array_add(array, taskobj);
+
+ snprintf(buf, sizeof(buf), "%p", task);
+ obj = json_object_new_string(buf);
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "id", obj);
+
+ if (task->name[0] != 0) {
+ obj = json_object_new_string(task->name);
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "name", obj);
+ }
+
+ obj = json_object_new_int(
+ isc_refcount_current(&task->references));
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "references", obj);
+
+ obj = json_object_new_string(statenames[task->state]);
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "state", obj);
+
+ obj = json_object_new_int(task->quantum);
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "quantum", obj);
+
+ obj = json_object_new_int(task->nevents);
+ CHECKMEM(obj);
+ json_object_object_add(taskobj, "events", obj);
+
+ UNLOCK(&task->lock);
+ }
+
+ json_object_object_add(tasks, "tasks", array);
+ array = NULL;
+ result = ISC_R_SUCCESS;
+
+error:
+ if (array != NULL) {
+ json_object_put(array);
+ }
+
+ if (task != NULL) {
+ UNLOCK(&task->lock);
+ }
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+#endif /* ifdef HAVE_JSON_C */
diff --git a/lib/isc/task_p.h b/lib/isc/task_p.h
new file mode 100644
index 0000000..5fc50b0
--- /dev/null
+++ b/lib/isc/task_p.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/mem.h>
+#include <isc/result.h>
+#include <isc/task.h>
+
+isc_result_t
+isc__taskmgr_create(isc_mem_t *mctx, unsigned int default_quantum, isc_nm_t *nm,
+ isc_taskmgr_t **managerp);
+/*%<
+ * Create a new task manager.
+ *
+ * Notes:
+ *
+ *\li If 'default_quantum' is non-zero, then it will be used as the default
+ * quantum value when tasks are created. If zero, then an implementation
+ * defined default quantum will be used.
+ *
+ *\li If 'nm' is set then netmgr is paused when an exclusive task mode
+ * is requested.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li managerp != NULL && *managerp == NULL
+ *
+ * Ensures:
+ *
+ *\li On success, '*managerp' will be attached to the newly created task
+ * manager.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li #ISC_R_NOTHREADS No threads could be created.
+ *\li #ISC_R_UNEXPECTED An unexpected error occurred.
+ *\li #ISC_R_SHUTTINGDOWN The non-threaded, shared, task
+ * manager shutting down.
+ */
+
+void
+isc__taskmgr_destroy(isc_taskmgr_t **managerp);
+/*%<
+ * Destroy '*managerp'.
+ *
+ * Notes:
+ *
+ *\li Calling isc_taskmgr_destroy() will shutdown all tasks managed by
+ * *managerp that haven't already been shutdown. The call will block
+ * until all tasks have entered the done state.
+ *
+ *\li isc_taskmgr_destroy() must not be called by a task event action,
+ * because it would block forever waiting for the event action to
+ * complete. An event action that wants to cause task manager shutdown
+ * should request some non-event action thread of execution to do the
+ * shutdown, e.g. by signaling a condition variable or using
+ * isc_app_shutdown().
+ *
+ *\li Task manager references are not reference counted, so the caller
+ * must ensure that no attempt will be made to use the manager after
+ * isc_taskmgr_destroy() returns.
+ *
+ * Requires:
+ *
+ *\li '*managerp' is a valid task manager.
+ *
+ *\li 'isc__taskmgr_shutdown()' and isc__netmgr_shutdown() have been
+ * called.
+ */
+
+void
+isc__taskmgr_shutdown(isc_taskmgr_t *manager);
+/*%>
+ * Shutdown 'manager'.
+ *
+ * Notes:
+ *
+ *\li Calling isc__taskmgr_shutdown() will shut down all tasks managed by
+ * *managerp that haven't already been shut down.
+ *
+ * Requires:
+ *
+ *\li 'manager' is a valid task manager.
+ *
+ *\li isc_taskmgr_destroy() has not be called previously on '*managerp'.
+ *
+ * Ensures:
+ *
+ *\li All resources used by the task manager, and any tasks it managed,
+ * have been freed.
+ */
diff --git a/lib/isc/taskpool.c b/lib/isc/taskpool.c
new file mode 100644
index 0000000..3099532
--- /dev/null
+++ b/lib/isc/taskpool.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/random.h>
+#include <isc/taskpool.h>
+#include <isc/util.h>
+
+/***
+ *** Types.
+ ***/
+
+struct isc_taskpool {
+ isc_mem_t *mctx;
+ isc_taskmgr_t *tmgr;
+ unsigned int ntasks;
+ unsigned int quantum;
+ isc_task_t **tasks;
+};
+
+/***
+ *** Functions.
+ ***/
+
+static void
+alloc_pool(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks,
+ unsigned int quantum, isc_taskpool_t **poolp) {
+ isc_taskpool_t *pool;
+ unsigned int i;
+
+ pool = isc_mem_get(mctx, sizeof(*pool));
+
+ pool->mctx = NULL;
+ isc_mem_attach(mctx, &pool->mctx);
+ pool->ntasks = ntasks;
+ pool->quantum = quantum;
+ pool->tmgr = tmgr;
+ pool->tasks = isc_mem_get(mctx, ntasks * sizeof(isc_task_t *));
+ for (i = 0; i < ntasks; i++) {
+ pool->tasks[i] = NULL;
+ }
+
+ *poolp = pool;
+}
+
+isc_result_t
+isc_taskpool_create(isc_taskmgr_t *tmgr, isc_mem_t *mctx, unsigned int ntasks,
+ unsigned int quantum, bool priv, isc_taskpool_t **poolp) {
+ unsigned int i;
+ isc_taskpool_t *pool = NULL;
+
+ INSIST(ntasks > 0);
+
+ /* Allocate the pool structure */
+ alloc_pool(tmgr, mctx, ntasks, quantum, &pool);
+
+ /* Create the tasks */
+ for (i = 0; i < ntasks; i++) {
+ isc_result_t result = isc_task_create_bound(tmgr, quantum,
+ &pool->tasks[i], i);
+ if (result != ISC_R_SUCCESS) {
+ isc_taskpool_destroy(&pool);
+ return (result);
+ }
+ isc_task_setprivilege(pool->tasks[i], priv);
+ isc_task_setname(pool->tasks[i], "taskpool", NULL);
+ }
+
+ *poolp = pool;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_taskpool_gettask(isc_taskpool_t *pool, isc_task_t **targetp) {
+ isc_task_attach(pool->tasks[isc_random_uniform(pool->ntasks)], targetp);
+}
+
+int
+isc_taskpool_size(isc_taskpool_t *pool) {
+ REQUIRE(pool != NULL);
+ return (pool->ntasks);
+}
+
+isc_result_t
+isc_taskpool_expand(isc_taskpool_t **sourcep, unsigned int size, bool priv,
+ isc_taskpool_t **targetp) {
+ isc_taskpool_t *pool;
+
+ REQUIRE(sourcep != NULL && *sourcep != NULL);
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ pool = *sourcep;
+ *sourcep = NULL;
+ if (size > pool->ntasks) {
+ isc_taskpool_t *newpool = NULL;
+ unsigned int i;
+
+ /* Allocate a new pool structure */
+ alloc_pool(pool->tmgr, pool->mctx, size, pool->quantum,
+ &newpool);
+
+ /* Copy over the tasks from the old pool */
+ for (i = 0; i < pool->ntasks; i++) {
+ newpool->tasks[i] = pool->tasks[i];
+ pool->tasks[i] = NULL;
+ }
+
+ /* Create new tasks */
+ for (i = pool->ntasks; i < size; i++) {
+ isc_result_t result =
+ isc_task_create_bound(pool->tmgr, pool->quantum,
+ &newpool->tasks[i], i);
+ if (result != ISC_R_SUCCESS) {
+ *sourcep = pool;
+ isc_taskpool_destroy(&newpool);
+ return (result);
+ }
+ isc_task_setprivilege(newpool->tasks[i], priv);
+ isc_task_setname(newpool->tasks[i], "taskpool", NULL);
+ }
+
+ isc_taskpool_destroy(&pool);
+ pool = newpool;
+ }
+
+ *targetp = pool;
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_taskpool_destroy(isc_taskpool_t **poolp) {
+ unsigned int i;
+ isc_taskpool_t *pool = *poolp;
+ *poolp = NULL;
+ for (i = 0; i < pool->ntasks; i++) {
+ if (pool->tasks[i] != NULL) {
+ isc_task_detach(&pool->tasks[i]);
+ }
+ }
+ isc_mem_put(pool->mctx, pool->tasks,
+ pool->ntasks * sizeof(isc_task_t *));
+ isc_mem_putanddetach(&pool->mctx, pool, sizeof(*pool));
+}
diff --git a/lib/isc/thread.c b/lib/isc/thread.c
new file mode 100644
index 0000000..5b762ed
--- /dev/null
+++ b/lib/isc/thread.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 */
+
+#if defined(HAVE_SCHED_H)
+#include <sched.h>
+#endif /* if defined(HAVE_SCHED_H) */
+
+#if defined(HAVE_CPUSET_H)
+#include <sys/cpuset.h>
+#include <sys/param.h>
+#endif /* if defined(HAVE_CPUSET_H) */
+
+#if defined(HAVE_SYS_PROCSET_H)
+#include <sys/processor.h>
+#include <sys/procset.h>
+#include <sys/types.h>
+#endif /* if defined(HAVE_SYS_PROCSET_H) */
+
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "trampoline_p.h"
+
+#ifndef THREAD_MINSTACKSIZE
+#define THREAD_MINSTACKSIZE (1024U * 1024)
+#endif /* ifndef THREAD_MINSTACKSIZE */
+
+void
+isc_thread_create(isc_threadfunc_t func, isc_threadarg_t arg,
+ isc_thread_t *thread) {
+ pthread_attr_t attr;
+ isc__trampoline_t *trampoline_arg;
+
+ trampoline_arg = isc__trampoline_get(func, arg);
+
+#if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
+ defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE)
+ size_t stacksize;
+#endif /* if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
+ * defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) */
+ int ret;
+
+ pthread_attr_init(&attr);
+
+#if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
+ defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE)
+ ret = pthread_attr_getstacksize(&attr, &stacksize);
+ if (ret != 0) {
+ FATAL_SYSERROR(ret, "pthread_attr_getstacksize()");
+ }
+
+ if (stacksize < THREAD_MINSTACKSIZE) {
+ ret = pthread_attr_setstacksize(&attr, THREAD_MINSTACKSIZE);
+ if (ret != 0) {
+ FATAL_SYSERROR(ret, "pthread_attr_setstacksize()");
+ }
+ }
+#endif /* if defined(HAVE_PTHREAD_ATTR_GETSTACKSIZE) && \
+ * defined(HAVE_PTHREAD_ATTR_SETSTACKSIZE) */
+
+ ret = pthread_create(thread, &attr, isc__trampoline_run,
+ trampoline_arg);
+ if (ret != 0) {
+ FATAL_SYSERROR(ret, "pthread_create()");
+ }
+
+ pthread_attr_destroy(&attr);
+
+ return;
+}
+
+void
+isc_thread_join(isc_thread_t thread, isc_threadresult_t *result) {
+ int ret = pthread_join(thread, result);
+ if (ret != 0) {
+ FATAL_SYSERROR(ret, "pthread_join()");
+ }
+}
+
+void
+isc_thread_setname(isc_thread_t thread, const char *name) {
+#if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__)
+ /*
+ * macOS has pthread_setname_np but only works on the
+ * current thread so it's not used here
+ */
+#if defined(__NetBSD__)
+ (void)pthread_setname_np(thread, name, NULL);
+#else /* if defined(__NetBSD__) */
+ (void)pthread_setname_np(thread, name);
+#endif /* if defined(__NetBSD__) */
+#elif defined(HAVE_PTHREAD_SET_NAME_NP)
+ (void)pthread_set_name_np(thread, name);
+#else /* if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__) */
+ UNUSED(thread);
+ UNUSED(name);
+#endif /* if defined(HAVE_PTHREAD_SETNAME_NP) && !defined(__APPLE__) */
+}
+
+void
+isc_thread_yield(void) {
+#if defined(HAVE_SCHED_YIELD)
+ sched_yield();
+#elif defined(HAVE_PTHREAD_YIELD)
+ pthread_yield();
+#elif defined(HAVE_PTHREAD_YIELD_NP)
+ pthread_yield_np();
+#endif /* if defined(HAVE_SCHED_YIELD) */
+}
diff --git a/lib/isc/time.c b/lib/isc/time.c
new file mode 100644
index 0000000..b03f377
--- /dev/null
+++ b/lib/isc/time.c
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/time.h> /* Required for struct timeval on some platforms. */
+#include <syslog.h>
+#include <time.h>
+
+#include <isc/log.h>
+#include <isc/print.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/tm.h>
+#include <isc/util.h>
+
+#if defined(CLOCK_REALTIME)
+#define CLOCKSOURCE_HIRES CLOCK_REALTIME
+#endif /* #if defined(CLOCK_REALTIME) */
+
+#if defined(CLOCK_REALTIME_COARSE)
+#define CLOCKSOURCE CLOCK_REALTIME_COARSE
+#elif defined(CLOCK_REALTIME_FAST)
+#define CLOCKSOURCE CLOCK_REALTIME_FAST
+#else /* if defined(CLOCK_REALTIME_COARSE) */
+#define CLOCKSOURCE CLOCK_REALTIME
+#endif /* if defined(CLOCK_REALTIME_COARSE) */
+
+#if !defined(CLOCKSOURCE_HIRES)
+#define CLOCKSOURCE_HIRES CLOCKSOURCE
+#endif /* #ifndef CLOCKSOURCE_HIRES */
+
+/*%
+ *** Intervals
+ ***/
+
+#if !defined(UNIT_TESTING)
+static const isc_interval_t zero_interval = { 0, 0 };
+const isc_interval_t *const isc_interval_zero = &zero_interval;
+#endif
+
+void
+isc_interval_set(isc_interval_t *i, unsigned int seconds,
+ unsigned int nanoseconds) {
+ REQUIRE(i != NULL);
+ REQUIRE(nanoseconds < NS_PER_SEC);
+
+ i->seconds = seconds;
+ i->nanoseconds = nanoseconds;
+}
+
+bool
+isc_interval_iszero(const isc_interval_t *i) {
+ REQUIRE(i != NULL);
+ INSIST(i->nanoseconds < NS_PER_SEC);
+
+ if (i->seconds == 0 && i->nanoseconds == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+unsigned int
+isc_interval_ms(const isc_interval_t *i) {
+ REQUIRE(i != NULL);
+ INSIST(i->nanoseconds < NS_PER_SEC);
+
+ return ((i->seconds * MS_PER_SEC) + (i->nanoseconds / NS_PER_MS));
+}
+
+/***
+ *** Absolute Times
+ ***/
+
+#if !defined(UNIT_TESTING)
+static const isc_time_t epoch = { 0, 0 };
+const isc_time_t *const isc_time_epoch = &epoch;
+#endif
+
+void
+isc_time_set(isc_time_t *t, unsigned int seconds, unsigned int nanoseconds) {
+ REQUIRE(t != NULL);
+ REQUIRE(nanoseconds < NS_PER_SEC);
+
+ t->seconds = seconds;
+ t->nanoseconds = nanoseconds;
+}
+
+void
+isc_time_settoepoch(isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ t->seconds = 0;
+ t->nanoseconds = 0;
+}
+
+bool
+isc_time_isepoch(const isc_time_t *t) {
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+
+ if (t->seconds == 0 && t->nanoseconds == 0) {
+ return (true);
+ }
+
+ return (false);
+}
+
+static isc_result_t
+time_now(isc_time_t *t, clockid_t clock) {
+ struct timespec ts;
+
+ REQUIRE(t != NULL);
+
+ if (clock_gettime(clock, &ts) == -1) {
+ UNEXPECTED_SYSERROR(errno, "clock_gettime()");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_SEC) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Ensure the tv_sec value fits in t->seconds.
+ */
+ if (sizeof(ts.tv_sec) > sizeof(t->seconds) &&
+ ((ts.tv_sec | (unsigned int)-1) ^ (unsigned int)-1) != 0U)
+ {
+ return (ISC_R_RANGE);
+ }
+
+ t->seconds = ts.tv_sec;
+ t->nanoseconds = ts.tv_nsec;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_time_now_hires(isc_time_t *t) {
+ return time_now(t, CLOCKSOURCE_HIRES);
+}
+
+isc_result_t
+isc_time_now(isc_time_t *t) {
+ return time_now(t, CLOCKSOURCE);
+}
+
+isc_result_t
+isc_time_nowplusinterval(isc_time_t *t, const isc_interval_t *i) {
+ struct timespec ts;
+
+ REQUIRE(t != NULL);
+ REQUIRE(i != NULL);
+ INSIST(i->nanoseconds < NS_PER_SEC);
+
+ if (clock_gettime(CLOCKSOURCE, &ts) == -1) {
+ UNEXPECTED_SYSERROR(errno, "clock_gettime()");
+ return (ISC_R_UNEXPECTED);
+ }
+
+ if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= NS_PER_SEC) {
+ return (ISC_R_UNEXPECTED);
+ }
+
+ /*
+ * Ensure the resulting seconds value fits in the size of an
+ * unsigned int. (It is written this way as a slight optimization;
+ * note that even if both values == INT_MAX, then when added
+ * and getting another 1 added below the result is UINT_MAX.)
+ */
+ if ((ts.tv_sec > INT_MAX || i->seconds > INT_MAX) &&
+ ((long long)ts.tv_sec + i->seconds > UINT_MAX))
+ {
+ return (ISC_R_RANGE);
+ }
+
+ t->seconds = ts.tv_sec + i->seconds;
+ t->nanoseconds = ts.tv_nsec + i->nanoseconds;
+ if (t->nanoseconds >= NS_PER_SEC) {
+ t->seconds++;
+ t->nanoseconds -= NS_PER_SEC;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+int
+isc_time_compare(const isc_time_t *t1, const isc_time_t *t2) {
+ REQUIRE(t1 != NULL && t2 != NULL);
+ INSIST(t1->nanoseconds < NS_PER_SEC && t2->nanoseconds < NS_PER_SEC);
+
+ if (t1->seconds < t2->seconds) {
+ return (-1);
+ }
+ if (t1->seconds > t2->seconds) {
+ return (1);
+ }
+ if (t1->nanoseconds < t2->nanoseconds) {
+ return (-1);
+ }
+ if (t1->nanoseconds > t2->nanoseconds) {
+ return (1);
+ }
+ return (0);
+}
+
+isc_result_t
+isc_time_add(const isc_time_t *t, const isc_interval_t *i, isc_time_t *result) {
+ REQUIRE(t != NULL && i != NULL && result != NULL);
+ REQUIRE(t->nanoseconds < NS_PER_SEC && i->nanoseconds < NS_PER_SEC);
+
+ /* Seconds */
+#if HAVE_BUILTIN_OVERFLOW
+ if (__builtin_uadd_overflow(t->seconds, i->seconds, &result->seconds)) {
+ return (ISC_R_RANGE);
+ }
+#else
+ if (t->seconds > UINT_MAX - i->seconds) {
+ return (ISC_R_RANGE);
+ }
+ result->seconds = t->seconds + i->seconds;
+#endif
+
+ /* Nanoseconds */
+ result->nanoseconds = t->nanoseconds + i->nanoseconds;
+ if (result->nanoseconds >= NS_PER_SEC) {
+ if (result->seconds == UINT_MAX) {
+ return (ISC_R_RANGE);
+ }
+ result->nanoseconds -= NS_PER_SEC;
+ result->seconds++;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_time_subtract(const isc_time_t *t, const isc_interval_t *i,
+ isc_time_t *result) {
+ REQUIRE(t != NULL && i != NULL && result != NULL);
+ REQUIRE(t->nanoseconds < NS_PER_SEC && i->nanoseconds < NS_PER_SEC);
+
+ /* Seconds */
+#if HAVE_BUILTIN_OVERFLOW
+ if (__builtin_usub_overflow(t->seconds, i->seconds, &result->seconds)) {
+ return (ISC_R_RANGE);
+ }
+#else
+ if (t->seconds < i->seconds) {
+ return (ISC_R_RANGE);
+ }
+ result->seconds = t->seconds - i->seconds;
+#endif
+
+ /* Nanoseconds */
+ if (t->nanoseconds >= i->nanoseconds) {
+ result->nanoseconds = t->nanoseconds - i->nanoseconds;
+ } else {
+ if (result->seconds == 0) {
+ return (ISC_R_RANGE);
+ }
+ result->seconds--;
+ result->nanoseconds = NS_PER_SEC + t->nanoseconds -
+ i->nanoseconds;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+uint64_t
+isc_time_microdiff(const isc_time_t *t1, const isc_time_t *t2) {
+ uint64_t i1, i2, i3;
+
+ REQUIRE(t1 != NULL && t2 != NULL);
+ INSIST(t1->nanoseconds < NS_PER_SEC && t2->nanoseconds < NS_PER_SEC);
+
+ i1 = (uint64_t)t1->seconds * NS_PER_SEC + t1->nanoseconds;
+ i2 = (uint64_t)t2->seconds * NS_PER_SEC + t2->nanoseconds;
+
+ if (i1 <= i2) {
+ return (0);
+ }
+
+ i3 = i1 - i2;
+
+ /*
+ * Convert to microseconds.
+ */
+ i3 /= NS_PER_US;
+
+ return (i3);
+}
+
+uint32_t
+isc_time_seconds(const isc_time_t *t) {
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+
+ return ((uint32_t)t->seconds);
+}
+
+isc_result_t
+isc_time_secondsastimet(const isc_time_t *t, time_t *secondsp) {
+ time_t seconds;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+
+ /*
+ * Ensure that the number of seconds represented by t->seconds
+ * can be represented by a time_t. Since t->seconds is an
+ * unsigned int and since time_t is mostly opaque, this is
+ * trickier than it seems. (This standardized opaqueness of
+ * time_t is *very* frustrating; time_t is not even limited to
+ * being an integral type.)
+ *
+ * The mission, then, is to avoid generating any kind of warning
+ * about "signed versus unsigned" while trying to determine if
+ * the unsigned int t->seconds is out range for tv_sec,
+ * which is pretty much only true if time_t is a signed integer
+ * of the same size as the return value of isc_time_seconds.
+ *
+ * If the paradox in the if clause below is true, t->seconds is
+ * out of range for time_t.
+ */
+ seconds = (time_t)t->seconds;
+
+ INSIST(sizeof(unsigned int) == sizeof(uint32_t));
+ INSIST(sizeof(time_t) >= sizeof(uint32_t));
+
+ if (t->seconds > (~0U >> 1) && seconds <= (time_t)(~0U >> 1)) {
+ return (ISC_R_RANGE);
+ }
+
+ *secondsp = seconds;
+
+ return (ISC_R_SUCCESS);
+}
+
+uint32_t
+isc_time_nanoseconds(const isc_time_t *t) {
+ REQUIRE(t != NULL);
+
+ ENSURE(t->nanoseconds < NS_PER_SEC);
+
+ return ((uint32_t)t->nanoseconds);
+}
+
+void
+isc_time_formattimestamp(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%d-%b-%Y %X", localtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen != 0) {
+ snprintf(buf + flen, len - flen, ".%03u",
+ t->nanoseconds / NS_PER_MS);
+ } else {
+ strlcpy(buf, "99-Bad-9999 99:99:99.999", len);
+ }
+}
+
+void
+isc_time_formathttptimestamp(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ /*
+ * 5 spaces, 1 comma, 3 GMT, 2 %d, 4 %Y, 8 %H:%M:%S, 3+ %a, 3+
+ * %b (29+)
+ */
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%a, %d %b %Y %H:%M:%S GMT",
+ gmtime_r(&now, &tm));
+ INSIST(flen < len);
+}
+
+isc_result_t
+isc_time_parsehttptimestamp(char *buf, isc_time_t *t) {
+ struct tm t_tm;
+ time_t when;
+ char *p;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(t != NULL);
+
+ p = isc_tm_strptime(buf, "%a, %d %b %Y %H:%M:%S", &t_tm);
+ if (p == NULL) {
+ return (ISC_R_UNEXPECTED);
+ }
+ when = isc_tm_timegm(&t_tm);
+ if (when == -1) {
+ return (ISC_R_UNEXPECTED);
+ }
+ isc_time_set(t, when, 0);
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_time_formatISO8601L(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm));
+ INSIST(flen < len);
+}
+
+void
+isc_time_formatISO8601Lms(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 6) {
+ snprintf(buf + flen, len - flen, ".%03u",
+ t->nanoseconds / NS_PER_MS);
+ }
+}
+
+void
+isc_time_formatISO8601Lus(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%S", localtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 6) {
+ snprintf(buf + flen, len - flen, ".%06u",
+ t->nanoseconds / NS_PER_US);
+ }
+}
+
+void
+isc_time_formatISO8601(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm));
+ INSIST(flen < len);
+}
+
+void
+isc_time_formatISO8601ms(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 5) {
+ flen -= 1; /* rewind one character (Z) */
+ snprintf(buf + flen, len - flen, ".%03uZ",
+ t->nanoseconds / NS_PER_MS);
+ }
+}
+
+void
+isc_time_formatISO8601us(const isc_time_t *t, char *buf, unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", gmtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 5) {
+ flen -= 1; /* rewind one character (Z) */
+ snprintf(buf + flen, len - flen, ".%06uZ",
+ t->nanoseconds / NS_PER_US);
+ }
+}
+
+void
+isc_time_formatshorttimestamp(const isc_time_t *t, char *buf,
+ unsigned int len) {
+ time_t now;
+ unsigned int flen;
+ struct tm tm;
+
+ REQUIRE(t != NULL);
+ INSIST(t->nanoseconds < NS_PER_SEC);
+ REQUIRE(buf != NULL);
+ REQUIRE(len > 0);
+
+ now = (time_t)t->seconds;
+ flen = strftime(buf, len, "%Y%m%d%H%M%S", gmtime_r(&now, &tm));
+ INSIST(flen < len);
+ if (flen > 0U && len - flen >= 5) {
+ snprintf(buf + flen, len - flen, "%03u",
+ t->nanoseconds / NS_PER_MS);
+ }
+}
diff --git a/lib/isc/timer.c b/lib/isc/timer.c
new file mode 100644
index 0000000..40ae929
--- /dev/null
+++ b/lib/isc/timer.c
@@ -0,0 +1,741 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/app.h>
+#include <isc/condition.h>
+#include <isc/heap.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/result.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/time.h>
+#include <isc/timer.h>
+#include <isc/util.h>
+
+#include "timer_p.h"
+
+#ifdef ISC_TIMER_TRACE
+#define XTRACE(s) fprintf(stderr, "%s\n", (s))
+#define XTRACEID(s, t) fprintf(stderr, "%s %p\n", (s), (t))
+#define XTRACETIME(s, d) \
+ fprintf(stderr, "%s %u.%09u\n", (s), (d).seconds, (d).nanoseconds)
+#define XTRACETIME2(s, d, n) \
+ fprintf(stderr, "%s %u.%09u %u.%09u\n", (s), (d).seconds, \
+ (d).nanoseconds, (n).seconds, (n).nanoseconds)
+#define XTRACETIMER(s, t, d) \
+ fprintf(stderr, "%s %p %u.%09u\n", (s), (t), (d).seconds, \
+ (d).nanoseconds)
+#else /* ifdef ISC_TIMER_TRACE */
+#define XTRACE(s)
+#define XTRACEID(s, t)
+#define XTRACETIME(s, d)
+#define XTRACETIME2(s, d, n)
+#define XTRACETIMER(s, t, d)
+#endif /* ISC_TIMER_TRACE */
+
+#define TIMER_MAGIC ISC_MAGIC('T', 'I', 'M', 'R')
+#define VALID_TIMER(t) ISC_MAGIC_VALID(t, TIMER_MAGIC)
+
+struct isc_timer {
+ /*! Not locked. */
+ unsigned int magic;
+ isc_timermgr_t *manager;
+ isc_mutex_t lock;
+ /*! Locked by timer lock. */
+ isc_time_t idle;
+ ISC_LIST(isc_timerevent_t) active;
+ /*! Locked by manager lock. */
+ isc_timertype_t type;
+ isc_time_t expires;
+ isc_interval_t interval;
+ isc_task_t *task;
+ isc_taskaction_t action;
+ void *arg;
+ unsigned int index;
+ isc_time_t due;
+ LINK(isc_timer_t) link;
+};
+
+#define TIMER_MANAGER_MAGIC ISC_MAGIC('T', 'I', 'M', 'M')
+#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, TIMER_MANAGER_MAGIC)
+
+struct isc_timermgr {
+ /* Not locked. */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+ /* Locked by manager lock. */
+ bool done;
+ LIST(isc_timer_t) timers;
+ unsigned int nscheduled;
+ isc_time_t due;
+ isc_condition_t wakeup;
+ isc_thread_t thread;
+ isc_heap_t *heap;
+};
+
+void
+isc_timermgr_poke(isc_timermgr_t *manager);
+
+static inline isc_result_t
+schedule(isc_timer_t *timer, isc_time_t *now, bool signal_ok) {
+ isc_timermgr_t *manager;
+ isc_time_t due;
+
+ /*!
+ * Note: the caller must ensure locking.
+ */
+
+ REQUIRE(timer->type != isc_timertype_inactive);
+
+ manager = timer->manager;
+
+ /*
+ * Compute the new due time.
+ */
+ if (timer->type != isc_timertype_once) {
+ isc_result_t result = isc_time_add(now, &timer->interval, &due);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (timer->type == isc_timertype_limited &&
+ isc_time_compare(&timer->expires, &due) < 0)
+ {
+ due = timer->expires;
+ }
+ } else {
+ if (isc_time_isepoch(&timer->idle)) {
+ due = timer->expires;
+ } else if (isc_time_isepoch(&timer->expires)) {
+ due = timer->idle;
+ } else if (isc_time_compare(&timer->idle, &timer->expires) < 0)
+ {
+ due = timer->idle;
+ } else {
+ due = timer->expires;
+ }
+ }
+
+ /*
+ * Schedule the timer.
+ */
+
+ if (timer->index > 0) {
+ /*
+ * Already scheduled.
+ */
+ int cmp = isc_time_compare(&due, &timer->due);
+ timer->due = due;
+ switch (cmp) {
+ case -1:
+ isc_heap_increased(manager->heap, timer->index);
+ break;
+ case 1:
+ isc_heap_decreased(manager->heap, timer->index);
+ break;
+ case 0:
+ /* Nothing to do. */
+ break;
+ }
+ } else {
+ timer->due = due;
+ isc_heap_insert(manager->heap, timer);
+ manager->nscheduled++;
+ }
+
+ XTRACETIMER("schedule", timer, due);
+
+ /*
+ * If this timer is at the head of the queue, we need to ensure
+ * that we won't miss it if it has a more recent due time than
+ * the current "next" timer. We do this either by waking up the
+ * run thread, or explicitly setting the value in the manager.
+ */
+
+ if (timer->index == 1 && signal_ok) {
+ XTRACE("signal (schedule)");
+ SIGNAL(&manager->wakeup);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static inline void
+deschedule(isc_timer_t *timer) {
+ isc_timermgr_t *manager;
+
+ /*
+ * The caller must ensure locking.
+ */
+
+ manager = timer->manager;
+ if (timer->index > 0) {
+ bool need_wakeup = false;
+ if (timer->index == 1) {
+ need_wakeup = true;
+ }
+ isc_heap_delete(manager->heap, timer->index);
+ timer->index = 0;
+ INSIST(manager->nscheduled > 0);
+ manager->nscheduled--;
+ if (need_wakeup) {
+ XTRACE("signal (deschedule)");
+ SIGNAL(&manager->wakeup);
+ }
+ }
+}
+
+static void
+timerevent_unlink(isc_timer_t *timer, isc_timerevent_t *event) {
+ REQUIRE(ISC_LINK_LINKED(event, ev_timerlink));
+ ISC_LIST_UNLINK(timer->active, event, ev_timerlink);
+}
+
+static void
+timerevent_destroy(isc_event_t *event0) {
+ isc_timer_t *timer = event0->ev_destroy_arg;
+ isc_timerevent_t *event = (isc_timerevent_t *)event0;
+
+ LOCK(&timer->lock);
+ if (ISC_LINK_LINKED(event, ev_timerlink)) {
+ /* The event was unlinked via timer_purge() */
+ timerevent_unlink(timer, event);
+ }
+ UNLOCK(&timer->lock);
+
+ isc_mem_put(timer->manager->mctx, event, event0->ev_size);
+}
+
+static void
+timer_purge(isc_timer_t *timer) {
+ isc_timerevent_t *event = NULL;
+
+ while ((event = ISC_LIST_HEAD(timer->active)) != NULL) {
+ timerevent_unlink(timer, event);
+ UNLOCK(&timer->lock);
+ (void)isc_task_purgeevent(timer->task, (isc_event_t *)event);
+ LOCK(&timer->lock);
+ }
+}
+
+isc_result_t
+isc_timer_create(isc_timermgr_t *manager, isc_timertype_t type,
+ const isc_time_t *expires, const isc_interval_t *interval,
+ isc_task_t *task, isc_taskaction_t action, void *arg,
+ isc_timer_t **timerp) {
+ REQUIRE(VALID_MANAGER(manager));
+ REQUIRE(task != NULL);
+ REQUIRE(action != NULL);
+
+ isc_timer_t *timer;
+ isc_result_t result;
+ isc_time_t now;
+
+ /*
+ * Create a new 'type' timer managed by 'manager'. The timers
+ * parameters are specified by 'expires' and 'interval'. Events
+ * will be posted to 'task' and when dispatched 'action' will be
+ * called with 'arg' as the arg value. The new timer is returned
+ * in 'timerp'.
+ */
+ if (expires == NULL) {
+ expires = isc_time_epoch;
+ }
+ if (interval == NULL) {
+ interval = isc_interval_zero;
+ }
+ REQUIRE(type == isc_timertype_inactive ||
+ !(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
+ REQUIRE(timerp != NULL && *timerp == NULL);
+ REQUIRE(type != isc_timertype_limited ||
+ !(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
+
+ /*
+ * Get current time.
+ */
+ if (type != isc_timertype_inactive) {
+ TIME_NOW(&now);
+ } else {
+ /*
+ * We don't have to do this, but it keeps the compiler from
+ * complaining about "now" possibly being used without being
+ * set, even though it will never actually happen.
+ */
+ isc_time_settoepoch(&now);
+ }
+
+ timer = isc_mem_get(manager->mctx, sizeof(*timer));
+
+ timer->manager = manager;
+
+ if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
+ result = isc_time_add(&now, interval, &timer->idle);
+ if (result != ISC_R_SUCCESS) {
+ isc_mem_put(manager->mctx, timer, sizeof(*timer));
+ return (result);
+ }
+ } else {
+ isc_time_settoepoch(&timer->idle);
+ }
+
+ timer->type = type;
+ timer->expires = *expires;
+ timer->interval = *interval;
+ timer->task = NULL;
+ isc_task_attach(task, &timer->task);
+ timer->action = action;
+ /*
+ * Removing the const attribute from "arg" is the best of two
+ * evils here. If the timer->arg member is made const, then
+ * it affects a great many recipients of the timer event
+ * which did not pass in an "arg" that was truly const.
+ * Changing isc_timer_create() to not have "arg" prototyped as const,
+ * though, can cause compilers warnings for calls that *do*
+ * have a truly const arg. The caller will have to carefully
+ * keep track of whether arg started as a true const.
+ */
+ DE_CONST(arg, timer->arg);
+ timer->index = 0;
+ isc_mutex_init(&timer->lock);
+ ISC_LINK_INIT(timer, link);
+
+ ISC_LIST_INIT(timer->active);
+
+ timer->magic = TIMER_MAGIC;
+
+ LOCK(&manager->lock);
+
+ /*
+ * Note we don't have to lock the timer like we normally would because
+ * there are no external references to it yet.
+ */
+
+ if (type != isc_timertype_inactive) {
+ result = schedule(timer, &now, true);
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ if (result == ISC_R_SUCCESS) {
+ *timerp = timer;
+ APPEND(manager->timers, timer, link);
+ }
+
+ UNLOCK(&manager->lock);
+
+ if (result != ISC_R_SUCCESS) {
+ timer->magic = 0;
+ isc_mutex_destroy(&timer->lock);
+ isc_task_detach(&timer->task);
+ isc_mem_put(manager->mctx, timer, sizeof(*timer));
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_timer_reset(isc_timer_t *timer, isc_timertype_t type,
+ const isc_time_t *expires, const isc_interval_t *interval,
+ bool purge) {
+ isc_time_t now;
+ isc_timermgr_t *manager;
+ isc_result_t result;
+
+ /*
+ * Change the timer's type, expires, and interval values to the given
+ * values. If 'purge' is true, any pending events from this timer
+ * are purged from its task's event queue.
+ */
+
+ REQUIRE(VALID_TIMER(timer));
+ manager = timer->manager;
+ REQUIRE(VALID_MANAGER(manager));
+
+ if (expires == NULL) {
+ expires = isc_time_epoch;
+ }
+ if (interval == NULL) {
+ interval = isc_interval_zero;
+ }
+ REQUIRE(type == isc_timertype_inactive ||
+ !(isc_time_isepoch(expires) && isc_interval_iszero(interval)));
+ REQUIRE(type != isc_timertype_limited ||
+ !(isc_time_isepoch(expires) || isc_interval_iszero(interval)));
+
+ /*
+ * Get current time.
+ */
+ if (type != isc_timertype_inactive) {
+ TIME_NOW(&now);
+ } else {
+ /*
+ * We don't have to do this, but it keeps the compiler from
+ * complaining about "now" possibly being used without being
+ * set, even though it will never actually happen.
+ */
+ isc_time_settoepoch(&now);
+ }
+
+ LOCK(&manager->lock);
+ LOCK(&timer->lock);
+
+ if (purge) {
+ timer_purge(timer);
+ }
+ timer->type = type;
+ timer->expires = *expires;
+ timer->interval = *interval;
+ if (type == isc_timertype_once && !isc_interval_iszero(interval)) {
+ result = isc_time_add(&now, interval, &timer->idle);
+ } else {
+ isc_time_settoepoch(&timer->idle);
+ result = ISC_R_SUCCESS;
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ if (type == isc_timertype_inactive) {
+ deschedule(timer);
+ result = ISC_R_SUCCESS;
+ } else {
+ result = schedule(timer, &now, true);
+ }
+ }
+
+ UNLOCK(&timer->lock);
+ UNLOCK(&manager->lock);
+
+ return (result);
+}
+
+isc_timertype_t
+isc_timer_gettype(isc_timer_t *timer) {
+ isc_timertype_t t;
+
+ REQUIRE(VALID_TIMER(timer));
+
+ LOCK(&timer->lock);
+ t = timer->type;
+ UNLOCK(&timer->lock);
+
+ return (t);
+}
+
+isc_result_t
+isc_timer_touch(isc_timer_t *timer) {
+ isc_result_t result;
+ isc_time_t now;
+
+ /*
+ * Set the last-touched time of 'timer' to the current time.
+ */
+
+ REQUIRE(VALID_TIMER(timer));
+
+ LOCK(&timer->lock);
+
+ /*
+ * We'd like to
+ *
+ * REQUIRE(timer->type == isc_timertype_once);
+ *
+ * but we cannot without locking the manager lock too, which we
+ * don't want to do.
+ */
+
+ TIME_NOW(&now);
+ result = isc_time_add(&now, &timer->interval, &timer->idle);
+
+ UNLOCK(&timer->lock);
+
+ return (result);
+}
+
+void
+isc_timer_destroy(isc_timer_t **timerp) {
+ isc_timer_t *timer = NULL;
+ isc_timermgr_t *manager = NULL;
+
+ REQUIRE(timerp != NULL && VALID_TIMER(*timerp));
+
+ timer = *timerp;
+ *timerp = NULL;
+
+ manager = timer->manager;
+
+ LOCK(&manager->lock);
+
+ LOCK(&timer->lock);
+ timer_purge(timer);
+ deschedule(timer);
+ UNLOCK(&timer->lock);
+
+ UNLINK(manager->timers, timer, link);
+
+ UNLOCK(&manager->lock);
+
+ isc_task_detach(&timer->task);
+ isc_mutex_destroy(&timer->lock);
+ timer->magic = 0;
+ isc_mem_put(manager->mctx, timer, sizeof(*timer));
+}
+
+static void
+timer_post_event(isc_timermgr_t *manager, isc_timer_t *timer,
+ isc_eventtype_t type) {
+ isc_timerevent_t *event;
+ XTRACEID("posting", timer);
+
+ event = (isc_timerevent_t *)isc_event_allocate(
+ manager->mctx, timer, type, timer->action, timer->arg,
+ sizeof(*event));
+
+ ISC_LINK_INIT(event, ev_timerlink);
+ ((isc_event_t *)event)->ev_destroy = timerevent_destroy;
+ ((isc_event_t *)event)->ev_destroy_arg = timer;
+
+ event->due = timer->due;
+
+ LOCK(&timer->lock);
+ ISC_LIST_APPEND(timer->active, event, ev_timerlink);
+ UNLOCK(&timer->lock);
+
+ isc_task_send(timer->task, ISC_EVENT_PTR(&event));
+}
+
+static void
+dispatch(isc_timermgr_t *manager, isc_time_t *now) {
+ bool done = false, post_event, need_schedule;
+ isc_eventtype_t type = 0;
+ isc_timer_t *timer;
+ isc_result_t result;
+ bool idle;
+
+ /*!
+ * The caller must be holding the manager lock.
+ */
+
+ while (manager->nscheduled > 0 && !done) {
+ timer = isc_heap_element(manager->heap, 1);
+ INSIST(timer != NULL && timer->type != isc_timertype_inactive);
+ if (isc_time_compare(now, &timer->due) >= 0) {
+ if (timer->type == isc_timertype_ticker) {
+ type = ISC_TIMEREVENT_TICK;
+ post_event = true;
+ need_schedule = true;
+ } else if (timer->type == isc_timertype_limited) {
+ int cmp;
+ cmp = isc_time_compare(now, &timer->expires);
+ if (cmp >= 0) {
+ type = ISC_TIMEREVENT_LIFE;
+ post_event = true;
+ need_schedule = false;
+ } else {
+ type = ISC_TIMEREVENT_TICK;
+ post_event = true;
+ need_schedule = true;
+ }
+ } else if (!isc_time_isepoch(&timer->expires) &&
+ isc_time_compare(now, &timer->expires) >= 0)
+ {
+ type = ISC_TIMEREVENT_LIFE;
+ post_event = true;
+ need_schedule = false;
+ } else {
+ idle = false;
+
+ LOCK(&timer->lock);
+ if (!isc_time_isepoch(&timer->idle) &&
+ isc_time_compare(now, &timer->idle) >= 0)
+ {
+ idle = true;
+ }
+ UNLOCK(&timer->lock);
+ if (idle) {
+ type = ISC_TIMEREVENT_IDLE;
+ post_event = true;
+ need_schedule = false;
+ } else {
+ /*
+ * Idle timer has been touched;
+ * reschedule.
+ */
+ XTRACEID("idle reschedule", timer);
+ post_event = false;
+ need_schedule = true;
+ }
+ }
+
+ if (post_event) {
+ timer_post_event(manager, timer, type);
+ }
+
+ timer->index = 0;
+ isc_heap_delete(manager->heap, 1);
+ manager->nscheduled--;
+
+ if (need_schedule) {
+ result = schedule(timer, now, false);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(
+ "couldn't schedule timer: %s",
+ isc_result_totext(result));
+ }
+ }
+ } else {
+ manager->due = timer->due;
+ done = true;
+ }
+ }
+}
+
+static isc_threadresult_t
+run(void *uap) {
+ isc_timermgr_t *manager = uap;
+ isc_time_t now;
+ isc_result_t result;
+
+ LOCK(&manager->lock);
+ while (!manager->done) {
+ TIME_NOW(&now);
+
+ XTRACETIME("running", now);
+
+ dispatch(manager, &now);
+
+ if (manager->nscheduled > 0) {
+ XTRACETIME2("waituntil", manager->due, now);
+ result = WAITUNTIL(&manager->wakeup, &manager->lock,
+ &manager->due);
+ INSIST(result == ISC_R_SUCCESS ||
+ result == ISC_R_TIMEDOUT);
+ } else {
+ XTRACETIME("wait", now);
+ WAIT(&manager->wakeup, &manager->lock);
+ }
+ XTRACE("wakeup");
+ }
+ UNLOCK(&manager->lock);
+
+ return ((isc_threadresult_t)0);
+}
+
+static bool
+sooner(void *v1, void *v2) {
+ isc_timer_t *t1, *t2;
+
+ t1 = v1;
+ t2 = v2;
+ REQUIRE(VALID_TIMER(t1));
+ REQUIRE(VALID_TIMER(t2));
+
+ if (isc_time_compare(&t1->due, &t2->due) < 0) {
+ return (true);
+ }
+ return (false);
+}
+
+static void
+set_index(void *what, unsigned int index) {
+ isc_timer_t *timer;
+
+ REQUIRE(VALID_TIMER(what));
+ timer = what;
+
+ timer->index = index;
+}
+
+isc_result_t
+isc__timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp) {
+ isc_timermgr_t *manager;
+
+ /*
+ * Create a timer manager.
+ */
+
+ REQUIRE(managerp != NULL && *managerp == NULL);
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+
+ manager->magic = TIMER_MANAGER_MAGIC;
+ manager->mctx = NULL;
+ manager->done = false;
+ INIT_LIST(manager->timers);
+ manager->nscheduled = 0;
+ isc_time_settoepoch(&manager->due);
+ manager->heap = NULL;
+ isc_heap_create(mctx, sooner, set_index, 0, &manager->heap);
+ isc_mutex_init(&manager->lock);
+ isc_mem_attach(mctx, &manager->mctx);
+ isc_condition_init(&manager->wakeup);
+ isc_thread_create(run, manager, &manager->thread);
+ isc_thread_setname(manager->thread, "isc-timer");
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isc_timermgr_poke(isc_timermgr_t *manager) {
+ REQUIRE(VALID_MANAGER(manager));
+
+ SIGNAL(&manager->wakeup);
+}
+
+void
+isc__timermgr_destroy(isc_timermgr_t **managerp) {
+ isc_timermgr_t *manager;
+
+ /*
+ * Destroy a timer manager.
+ */
+
+ REQUIRE(managerp != NULL);
+ manager = *managerp;
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&manager->lock);
+
+ REQUIRE(EMPTY(manager->timers));
+ manager->done = true;
+
+ XTRACE("signal (destroy)");
+ SIGNAL(&manager->wakeup);
+
+ UNLOCK(&manager->lock);
+
+ /*
+ * Wait for thread to exit.
+ */
+ isc_thread_join(manager->thread, NULL);
+
+ /*
+ * Clean up.
+ */
+ (void)isc_condition_destroy(&manager->wakeup);
+ isc_mutex_destroy(&manager->lock);
+ isc_heap_destroy(&manager->heap);
+ manager->magic = 0;
+ isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
+
+ *managerp = NULL;
+}
diff --git a/lib/isc/timer_p.h b/lib/isc/timer_p.h
new file mode 100644
index 0000000..ab9bf9c
--- /dev/null
+++ b/lib/isc/timer_p.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 <isc/mem.h>
+#include <isc/result.h>
+#include <isc/timer.h>
+
+isc_result_t
+isc__timermgr_create(isc_mem_t *mctx, isc_timermgr_t **managerp);
+/*%<
+ * Create a timer manager.
+ *
+ * Notes:
+ *
+ *\li All memory will be allocated in memory context 'mctx'.
+ *
+ * Requires:
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'managerp' points to a NULL isc_timermgr_t.
+ *
+ * Ensures:
+ *
+ *\li '*managerp' is a valid isc_timermgr_t.
+ *
+ * Returns:
+ *
+ *\li Success
+ *\li No memory
+ *\li Unexpected error
+ */
+
+void
+isc__timermgr_destroy(isc_timermgr_t **managerp);
+/*%<
+ * Destroy a timer manager.
+ *
+ * Notes:
+ *
+ *\li This routine blocks until there are no timers left in the manager,
+ * so if the caller holds any timer references using the manager, it
+ * must detach them before calling isc_timermgr_destroy() or it will
+ * block forever.
+ *
+ * Requires:
+ *
+ *\li '*managerp' is a valid isc_timermgr_t.
+ *
+ * Ensures:
+ *
+ *\li *managerp == NULL
+ *
+ *\li All resources used by the manager have been freed.
+ */
diff --git a/lib/isc/tls.c b/lib/isc/tls.c
new file mode 100644
index 0000000..e2cd63e
--- /dev/null
+++ b/lib/isc/tls.c
@@ -0,0 +1,1678 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <netinet/in.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#if HAVE_LIBNGHTTP2
+#include <nghttp2/nghttp2.h>
+#endif /* HAVE_LIBNGHTTP2 */
+#include <arpa/inet.h>
+
+#include <openssl/bn.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/opensslv.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/x509_vfy.h>
+#include <openssl/x509v3.h>
+
+#include <isc/atomic.h>
+#include <isc/ht.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/mutex.h>
+#include <isc/mutexblock.h>
+#include <isc/once.h>
+#include <isc/random.h>
+#include <isc/refcount.h>
+#include <isc/rwlock.h>
+#include <isc/sockaddr.h>
+#include <isc/thread.h>
+#include <isc/tls.h>
+#include <isc/util.h>
+
+#include "openssl_shim.h"
+#include "tls_p.h"
+
+#define COMMON_SSL_OPTIONS \
+ (SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION)
+
+static isc_once_t init_once = ISC_ONCE_INIT;
+static isc_once_t shut_once = ISC_ONCE_INIT;
+static atomic_bool init_done = false;
+static atomic_bool shut_done = false;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+static isc_mutex_t *locks = NULL;
+static int nlocks;
+
+static void
+isc__tls_lock_callback(int mode, int type, const char *file, int line) {
+ UNUSED(file);
+ UNUSED(line);
+ if ((mode & CRYPTO_LOCK) != 0) {
+ LOCK(&locks[type]);
+ } else {
+ UNLOCK(&locks[type]);
+ }
+}
+
+static void
+isc__tls_set_thread_id(CRYPTO_THREADID *id) {
+ CRYPTO_THREADID_set_numeric(id, (unsigned long)isc_thread_self());
+}
+#endif
+
+static void
+tls_initialize(void) {
+ REQUIRE(!atomic_load(&init_done));
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ RUNTIME_CHECK(OPENSSL_init_ssl(OPENSSL_INIT_ENGINE_ALL_BUILTIN |
+ OPENSSL_INIT_LOAD_CONFIG,
+ NULL) == 1);
+#else
+ nlocks = CRYPTO_num_locks();
+ /*
+ * We can't use isc_mem API here, because it's called too
+ * early and when the isc_mem_debugging flags are changed
+ * later.
+ *
+ * Actually, since this is a single allocation at library load
+ * and deallocation at library unload, using the standard
+ * allocator without the tracking is fine for this purpose.
+ */
+ locks = calloc(nlocks, sizeof(locks[0]));
+ isc_mutexblock_init(locks, nlocks);
+ CRYPTO_set_locking_callback(isc__tls_lock_callback);
+ CRYPTO_THREADID_set_callback(isc__tls_set_thread_id);
+
+ CRYPTO_malloc_init();
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ SSL_library_init();
+
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ ENGINE_load_builtin_engines();
+#endif
+ OpenSSL_add_all_algorithms();
+ OPENSSL_load_builtin_modules();
+
+ CONF_modules_load_file(NULL, NULL,
+ CONF_MFLAGS_DEFAULT_SECTION |
+ CONF_MFLAGS_IGNORE_MISSING_FILE);
+#endif
+
+ /* Protect ourselves against unseeded PRNG */
+ if (RAND_status() != 1) {
+ FATAL_ERROR("OpenSSL pseudorandom number generator "
+ "cannot be initialized (see the `PRNG not "
+ "seeded' message in the OpenSSL FAQ)");
+ }
+
+ atomic_compare_exchange_enforced(&init_done, &(bool){ false }, true);
+}
+
+void
+isc__tls_initialize(void) {
+ isc_result_t result = isc_once_do(&init_once, tls_initialize);
+ REQUIRE(result == ISC_R_SUCCESS);
+ REQUIRE(atomic_load(&init_done));
+}
+
+static void
+tls_shutdown(void) {
+ REQUIRE(atomic_load(&init_done));
+ REQUIRE(!atomic_load(&shut_done));
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ OPENSSL_cleanup();
+#else
+ CONF_modules_unload(1);
+ OBJ_cleanup();
+ EVP_cleanup();
+#if !defined(OPENSSL_NO_ENGINE) && OPENSSL_API_LEVEL < 30000
+ ENGINE_cleanup();
+#endif
+ CRYPTO_cleanup_all_ex_data();
+ ERR_remove_thread_state(NULL);
+ RAND_cleanup();
+ ERR_free_strings();
+
+ CRYPTO_set_locking_callback(NULL);
+
+ if (locks != NULL) {
+ isc_mutexblock_destroy(locks, nlocks);
+ free(locks);
+ locks = NULL;
+ }
+#endif
+
+ atomic_compare_exchange_enforced(&shut_done, &(bool){ false }, true);
+}
+
+void
+isc__tls_shutdown(void) {
+ isc_result_t result = isc_once_do(&shut_once, tls_shutdown);
+ REQUIRE(result == ISC_R_SUCCESS);
+ REQUIRE(atomic_load(&shut_done));
+}
+
+void
+isc_tlsctx_free(isc_tlsctx_t **ctxp) {
+ SSL_CTX *ctx = NULL;
+ REQUIRE(ctxp != NULL && *ctxp != NULL);
+
+ ctx = *ctxp;
+ *ctxp = NULL;
+
+ SSL_CTX_free(ctx);
+}
+
+void
+isc_tlsctx_attach(isc_tlsctx_t *src, isc_tlsctx_t **ptarget) {
+ REQUIRE(src != NULL);
+ REQUIRE(ptarget != NULL && *ptarget == NULL);
+
+ RUNTIME_CHECK(SSL_CTX_up_ref(src) == 1);
+
+ *ptarget = src;
+}
+
+#if HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
+/*
+ * Callback invoked by the SSL library whenever a new TLS pre-master secret
+ * needs to be logged.
+ */
+static void
+sslkeylogfile_append(const SSL *ssl, const char *line) {
+ UNUSED(ssl);
+
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_SSLKEYLOG, ISC_LOGMODULE_NETMGR,
+ ISC_LOG_INFO, "%s", line);
+}
+
+/*
+ * Enable TLS pre-master secret logging if the SSLKEYLOGFILE environment
+ * variable is set. This needs to be done on a per-context basis as that is
+ * how SSL_CTX_set_keylog_callback() works.
+ */
+static void
+sslkeylogfile_init(isc_tlsctx_t *ctx) {
+ if (getenv("SSLKEYLOGFILE") != NULL) {
+ SSL_CTX_set_keylog_callback(ctx, sslkeylogfile_append);
+ }
+}
+#else /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
+#define sslkeylogfile_init(ctx)
+#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
+
+isc_result_t
+isc_tlsctx_createclient(isc_tlsctx_t **ctxp) {
+ unsigned long err;
+ char errbuf[256];
+ SSL_CTX *ctx = NULL;
+ const SSL_METHOD *method = NULL;
+
+ REQUIRE(ctxp != NULL && *ctxp == NULL);
+
+ method = TLS_client_method();
+ if (method == NULL) {
+ goto ssl_error;
+ }
+ ctx = SSL_CTX_new(method);
+ if (ctx == NULL) {
+ goto ssl_error;
+ }
+
+ SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
+
+#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+ SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
+#else
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
+#endif
+
+ sslkeylogfile_init(ctx);
+
+ *ctxp = ctx;
+
+ return (ISC_R_SUCCESS);
+
+ssl_error:
+ err = ERR_get_error();
+ ERR_error_string_n(err, errbuf, sizeof(errbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ ISC_LOG_ERROR, "Error initializing TLS context: %s",
+ errbuf);
+
+ return (ISC_R_TLSERROR);
+}
+
+isc_result_t
+isc_tlsctx_load_certificate(isc_tlsctx_t *ctx, const char *keyfile,
+ const char *certfile) {
+ int rv;
+ REQUIRE(ctx != NULL);
+ REQUIRE(keyfile != NULL);
+ REQUIRE(certfile != NULL);
+
+ rv = SSL_CTX_use_certificate_chain_file(ctx, certfile);
+ if (rv != 1) {
+ return (ISC_R_TLSERROR);
+ }
+ rv = SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM);
+ if (rv != 1) {
+ return (ISC_R_TLSERROR);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_tlsctx_createserver(const char *keyfile, const char *certfile,
+ isc_tlsctx_t **ctxp) {
+ int rv;
+ unsigned long err;
+ bool ephemeral = (keyfile == NULL && certfile == NULL);
+ X509 *cert = NULL;
+ EVP_PKEY *pkey = NULL;
+ SSL_CTX *ctx = NULL;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ EC_KEY *eckey = NULL;
+#else
+ EVP_PKEY_CTX *pkey_ctx = NULL;
+ EVP_PKEY *params_pkey = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
+ char errbuf[256];
+ const SSL_METHOD *method = NULL;
+
+ REQUIRE(ctxp != NULL && *ctxp == NULL);
+ REQUIRE((keyfile == NULL) == (certfile == NULL));
+
+ method = TLS_server_method();
+ if (method == NULL) {
+ goto ssl_error;
+ }
+ ctx = SSL_CTX_new(method);
+ if (ctx == NULL) {
+ goto ssl_error;
+ }
+ RUNTIME_CHECK(ctx != NULL);
+
+ SSL_CTX_set_options(ctx, COMMON_SSL_OPTIONS);
+
+#if HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+ SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
+#else
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
+#endif
+
+ if (ephemeral) {
+ const int group_nid = NID_X9_62_prime256v1;
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ eckey = EC_KEY_new_by_curve_name(group_nid);
+ if (eckey == NULL) {
+ goto ssl_error;
+ }
+
+ /* Generate the key. */
+ rv = EC_KEY_generate_key(eckey);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+ pkey = EVP_PKEY_new();
+ if (pkey == NULL) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_set1_EC_KEY(pkey, eckey);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+
+ /* Use a named curve and uncompressed point conversion form. */
+#if HAVE_EVP_PKEY_GET0_EC_KEY
+ EC_KEY_set_asn1_flag(EVP_PKEY_get0_EC_KEY(pkey),
+ OPENSSL_EC_NAMED_CURVE);
+ EC_KEY_set_conv_form(EVP_PKEY_get0_EC_KEY(pkey),
+ POINT_CONVERSION_UNCOMPRESSED);
+#else
+ EC_KEY_set_asn1_flag(pkey->pkey.ec, OPENSSL_EC_NAMED_CURVE);
+ EC_KEY_set_conv_form(pkey->pkey.ec,
+ POINT_CONVERSION_UNCOMPRESSED);
+#endif /* HAVE_EVP_PKEY_GET0_EC_KEY */
+
+#if defined(SSL_CTX_set_ecdh_auto)
+ /*
+ * Using this macro is required for older versions of OpenSSL to
+ * automatically enable ECDH support.
+ *
+ * On later versions this function is no longer needed and is
+ * deprecated.
+ */
+ (void)SSL_CTX_set_ecdh_auto(ctx, 1);
+#endif /* defined(SSL_CTX_set_ecdh_auto) */
+
+ /* Cleanup */
+ EC_KEY_free(eckey);
+ eckey = NULL;
+#else
+ /* Generate the key's parameters. */
+ pkey_ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
+ if (pkey_ctx == NULL) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_paramgen_init(pkey_ctx);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx,
+ group_nid);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_paramgen(pkey_ctx, &params_pkey);
+ if (rv != 1 || params_pkey == NULL) {
+ goto ssl_error;
+ }
+ EVP_PKEY_CTX_free(pkey_ctx);
+
+ /* Generate the key. */
+ pkey_ctx = EVP_PKEY_CTX_new(params_pkey, NULL);
+ if (pkey_ctx == NULL) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_keygen_init(pkey_ctx);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+ rv = EVP_PKEY_keygen(pkey_ctx, &pkey);
+ if (rv != 1 || pkey == NULL) {
+ goto ssl_error;
+ }
+
+ /* Cleanup */
+ EVP_PKEY_free(params_pkey);
+ params_pkey = NULL;
+ EVP_PKEY_CTX_free(pkey_ctx);
+ pkey_ctx = NULL;
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+ cert = X509_new();
+ if (cert == NULL) {
+ goto ssl_error;
+ }
+
+ ASN1_INTEGER_set(X509_get_serialNumber(cert),
+ (long)isc_random32());
+
+ /*
+ * Set the "not before" property 5 minutes into the past to
+ * accommodate with some possible clock skew across systems.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10101000L
+ X509_gmtime_adj(X509_get_notBefore(cert), -300);
+#else
+ X509_gmtime_adj(X509_getm_notBefore(cert), -300);
+#endif
+
+ /*
+ * We set the vailidy for 10 years.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10101000L
+ X509_gmtime_adj(X509_get_notAfter(cert), 3650 * 24 * 3600);
+#else
+ X509_gmtime_adj(X509_getm_notAfter(cert), 3650 * 24 * 3600);
+#endif
+
+ X509_set_pubkey(cert, pkey);
+
+ X509_NAME *name = X509_get_subject_name(cert);
+
+ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
+ (const unsigned char *)"AQ", -1, -1,
+ 0);
+ X509_NAME_add_entry_by_txt(
+ name, "O", MBSTRING_ASC,
+ (const unsigned char *)"BIND9 ephemeral "
+ "certificate",
+ -1, -1, 0);
+ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
+ (const unsigned char *)"bind9.local",
+ -1, -1, 0);
+
+ X509_set_issuer_name(cert, name);
+ X509_sign(cert, pkey, EVP_sha256());
+ rv = SSL_CTX_use_certificate(ctx, cert);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+ rv = SSL_CTX_use_PrivateKey(ctx, pkey);
+ if (rv != 1) {
+ goto ssl_error;
+ }
+
+ X509_free(cert);
+ EVP_PKEY_free(pkey);
+ } else {
+ isc_result_t result;
+ result = isc_tlsctx_load_certificate(ctx, keyfile, certfile);
+ if (result != ISC_R_SUCCESS) {
+ goto ssl_error;
+ }
+ }
+
+ sslkeylogfile_init(ctx);
+
+ *ctxp = ctx;
+ return (ISC_R_SUCCESS);
+
+ssl_error:
+ err = ERR_get_error();
+ ERR_error_string_n(err, errbuf, sizeof(errbuf));
+ isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL, ISC_LOGMODULE_NETMGR,
+ ISC_LOG_ERROR, "Error initializing TLS context: %s",
+ errbuf);
+
+ if (ctx != NULL) {
+ SSL_CTX_free(ctx);
+ }
+ if (cert != NULL) {
+ X509_free(cert);
+ }
+ if (pkey != NULL) {
+ EVP_PKEY_free(pkey);
+ }
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ if (eckey != NULL) {
+ EC_KEY_free(eckey);
+ }
+#else
+ if (params_pkey != NULL) {
+ EVP_PKEY_free(params_pkey);
+ }
+ if (pkey_ctx != NULL) {
+ EVP_PKEY_CTX_free(pkey_ctx);
+ }
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+ return (ISC_R_TLSERROR);
+}
+
+static long
+get_tls_version_disable_bit(const isc_tls_protocol_version_t tls_ver) {
+ long bit = 0;
+
+ switch (tls_ver) {
+ case ISC_TLS_PROTO_VER_1_2:
+#ifdef SSL_OP_NO_TLSv1_2
+ bit = SSL_OP_NO_TLSv1_2;
+#else
+ bit = 0;
+#endif
+ break;
+ case ISC_TLS_PROTO_VER_1_3:
+#ifdef SSL_OP_NO_TLSv1_3
+ bit = SSL_OP_NO_TLSv1_3;
+#else
+ bit = 0;
+#endif
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ };
+
+ return (bit);
+}
+
+bool
+isc_tls_protocol_supported(const isc_tls_protocol_version_t tls_ver) {
+ return (get_tls_version_disable_bit(tls_ver) != 0);
+}
+
+isc_tls_protocol_version_t
+isc_tls_protocol_name_to_version(const char *name) {
+ REQUIRE(name != NULL);
+
+ if (strcasecmp(name, "TLSv1.2") == 0) {
+ return (ISC_TLS_PROTO_VER_1_2);
+ } else if (strcasecmp(name, "TLSv1.3") == 0) {
+ return (ISC_TLS_PROTO_VER_1_3);
+ }
+
+ return (ISC_TLS_PROTO_VER_UNDEFINED);
+}
+
+void
+isc_tlsctx_set_protocols(isc_tlsctx_t *ctx, const uint32_t tls_versions) {
+ REQUIRE(ctx != NULL);
+ REQUIRE(tls_versions != 0);
+ long set_options = 0;
+ long clear_options = 0;
+ uint32_t versions = tls_versions;
+
+ /*
+ * The code below might be initially hard to follow because of the
+ * double negation that OpenSSL enforces.
+ *
+ * Taking into account that OpenSSL provides bits to *disable*
+ * specific protocol versions, like SSL_OP_NO_TLSv1_2,
+ * SSL_OP_NO_TLSv1_3, etc., the code has the following logic:
+ *
+ * If a protocol version is not specified in the bitmask, get the
+ * bit that disables it and add it to the set of TLS options to
+ * set ('set_options'). Otherwise, if a protocol version is set,
+ * add the bit to the set of options to clear ('clear_options').
+ */
+
+ /* TLS protocol versions are defined as powers of two. */
+ for (uint32_t tls_ver = ISC_TLS_PROTO_VER_1_2;
+ tls_ver < ISC_TLS_PROTO_VER_UNDEFINED; tls_ver <<= 1)
+ {
+ if ((tls_versions & tls_ver) == 0) {
+ set_options |= get_tls_version_disable_bit(tls_ver);
+ } else {
+ /*
+ * Only supported versions should ever be passed to the
+ * function SSL_CTX_clear_options. For example, in order
+ * to enable TLS v1.2, we have to clear
+ * SSL_OP_NO_TLSv1_2. Insist that the configuration file
+ * was verified properly, so we are not trying to enable
+ * an unsupported TLS version.
+ */
+ INSIST(isc_tls_protocol_supported(tls_ver));
+ clear_options |= get_tls_version_disable_bit(tls_ver);
+ }
+ versions &= ~(tls_ver);
+ }
+
+ /* All versions should be processed at this point, thus the value
+ * must equal zero. If it is not, then some garbage has been
+ * passed to the function; this situation is worth
+ * investigation. */
+ INSIST(versions == 0);
+
+ (void)SSL_CTX_set_options(ctx, set_options);
+ (void)SSL_CTX_clear_options(ctx, clear_options);
+}
+
+bool
+isc_tlsctx_load_dhparams(isc_tlsctx_t *ctx, const char *dhparams_file) {
+ REQUIRE(ctx != NULL);
+ REQUIRE(dhparams_file != NULL);
+ REQUIRE(*dhparams_file != '\0');
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ /* OpenSSL < 3.0 */
+ DH *dh = NULL;
+ FILE *paramfile;
+
+ paramfile = fopen(dhparams_file, "r");
+
+ if (paramfile) {
+ int check = 0;
+ dh = PEM_read_DHparams(paramfile, NULL, NULL, NULL);
+ fclose(paramfile);
+
+ if (dh == NULL) {
+ return (false);
+ } else if (DH_check(dh, &check) != 1 || check != 0) {
+ DH_free(dh);
+ return (false);
+ }
+ } else {
+ return (false);
+ }
+
+ if (SSL_CTX_set_tmp_dh(ctx, dh) != 1) {
+ DH_free(dh);
+ return (false);
+ }
+
+ DH_free(dh);
+#else
+ /* OpenSSL >= 3.0: low level DH APIs are deprecated in OpenSSL 3.0 */
+ EVP_PKEY *dh = NULL;
+ BIO *bio = NULL;
+
+ bio = BIO_new_file(dhparams_file, "r");
+ if (bio == NULL) {
+ return (false);
+ }
+
+ dh = PEM_read_bio_Parameters(bio, NULL);
+ if (dh == NULL) {
+ BIO_free(bio);
+ return (false);
+ }
+
+ if (SSL_CTX_set0_tmp_dh_pkey(ctx, dh) != 1) {
+ BIO_free(bio);
+ EVP_PKEY_free(dh);
+ return (false);
+ }
+
+ /* No need to call EVP_PKEY_free(dh) as the "dh" is owned by the
+ * SSL context at this point. */
+
+ BIO_free(bio);
+#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */
+
+ return (true);
+}
+
+bool
+isc_tls_cipherlist_valid(const char *cipherlist) {
+ isc_tlsctx_t *tmp_ctx = NULL;
+ const SSL_METHOD *method = NULL;
+ bool result;
+ REQUIRE(cipherlist != NULL);
+
+ if (*cipherlist == '\0') {
+ return (false);
+ }
+
+ method = TLS_server_method();
+ if (method == NULL) {
+ return (false);
+ }
+ tmp_ctx = SSL_CTX_new(method);
+ if (tmp_ctx == NULL) {
+ return (false);
+ }
+
+ result = SSL_CTX_set_cipher_list(tmp_ctx, cipherlist) == 1;
+
+ isc_tlsctx_free(&tmp_ctx);
+
+ return (result);
+}
+
+void
+isc_tlsctx_set_cipherlist(isc_tlsctx_t *ctx, const char *cipherlist) {
+ REQUIRE(ctx != NULL);
+ REQUIRE(cipherlist != NULL);
+ REQUIRE(*cipherlist != '\0');
+
+ RUNTIME_CHECK(SSL_CTX_set_cipher_list(ctx, cipherlist) == 1);
+}
+
+void
+isc_tlsctx_prefer_server_ciphers(isc_tlsctx_t *ctx, const bool prefer) {
+ REQUIRE(ctx != NULL);
+
+ if (prefer) {
+ (void)SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+ } else {
+ (void)SSL_CTX_clear_options(ctx,
+ SSL_OP_CIPHER_SERVER_PREFERENCE);
+ }
+}
+
+void
+isc_tlsctx_session_tickets(isc_tlsctx_t *ctx, const bool use) {
+ REQUIRE(ctx != NULL);
+
+ if (!use) {
+ (void)SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
+ } else {
+ (void)SSL_CTX_clear_options(ctx, SSL_OP_NO_TICKET);
+ }
+}
+
+isc_tls_t *
+isc_tls_create(isc_tlsctx_t *ctx) {
+ isc_tls_t *newctx = NULL;
+
+ REQUIRE(ctx != NULL);
+
+ newctx = SSL_new(ctx);
+ if (newctx == NULL) {
+ char errbuf[256];
+ unsigned long err = ERR_get_error();
+
+ ERR_error_string_n(err, errbuf, sizeof(errbuf));
+ fprintf(stderr, "%s:SSL_new(%p) -> %s\n", __func__, ctx,
+ errbuf);
+ }
+
+ return (newctx);
+}
+
+void
+isc_tls_free(isc_tls_t **tlsp) {
+ isc_tls_t *tls = NULL;
+ REQUIRE(tlsp != NULL && *tlsp != NULL);
+
+ tls = *tlsp;
+ *tlsp = NULL;
+ SSL_free(tls);
+}
+
+const char *
+isc_tls_verify_peer_result_string(isc_tls_t *tls) {
+ REQUIRE(tls != NULL);
+
+ return (X509_verify_cert_error_string(SSL_get_verify_result(tls)));
+}
+
+#if HAVE_LIBNGHTTP2
+#ifndef OPENSSL_NO_NEXTPROTONEG
+/*
+ * NPN TLS extension client callback.
+ */
+static int
+select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen, void *arg) {
+ UNUSED(ssl);
+ UNUSED(arg);
+
+ if (nghttp2_select_next_protocol(out, outlen, in, inlen) <= 0) {
+ return (SSL_TLSEXT_ERR_NOACK);
+ }
+ return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+void
+isc_tlsctx_enable_http2client_alpn(isc_tlsctx_t *ctx) {
+ REQUIRE(ctx != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+ SSL_CTX_set_next_proto_select_cb(ctx, select_next_proto_cb, NULL);
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)NGHTTP2_PROTO_ALPN,
+ NGHTTP2_PROTO_ALPN_LEN);
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+}
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+static int
+next_proto_cb(isc_tls_t *ssl, const unsigned char **data, unsigned int *len,
+ void *arg) {
+ UNUSED(ssl);
+ UNUSED(arg);
+
+ *data = (const unsigned char *)NGHTTP2_PROTO_ALPN;
+ *len = (unsigned int)NGHTTP2_PROTO_ALPN_LEN;
+ return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* !OPENSSL_NO_NEXTPROTONEG */
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+static int
+alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen,
+ const unsigned char *in, unsigned int inlen, void *arg) {
+ int ret;
+
+ UNUSED(ssl);
+ UNUSED(arg);
+
+ ret = nghttp2_select_next_protocol((unsigned char **)(uintptr_t)out,
+ outlen, in, inlen);
+
+ if (ret != 1) {
+ return (SSL_TLSEXT_ERR_NOACK);
+ }
+
+ return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+
+void
+isc_tlsctx_enable_http2server_alpn(isc_tlsctx_t *tls) {
+ REQUIRE(tls != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+ SSL_CTX_set_next_protos_advertised_cb(tls, next_proto_cb, NULL);
+#endif // OPENSSL_NO_NEXTPROTONEG
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_CTX_set_alpn_select_cb(tls, alpn_select_proto_cb, NULL);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+}
+#endif /* HAVE_LIBNGHTTP2 */
+
+void
+isc_tls_get_selected_alpn(isc_tls_t *tls, const unsigned char **alpn,
+ unsigned int *alpnlen) {
+ REQUIRE(tls != NULL);
+ REQUIRE(alpn != NULL);
+ REQUIRE(alpnlen != NULL);
+
+#ifndef OPENSSL_NO_NEXTPROTONEG
+ SSL_get0_next_proto_negotiated(tls, alpn, alpnlen);
+#endif
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ if (*alpn == NULL) {
+ SSL_get0_alpn_selected(tls, alpn, alpnlen);
+ }
+#endif
+}
+
+static bool
+protoneg_check_protocol(const uint8_t **pout, uint8_t *pout_len,
+ const uint8_t *in, size_t in_len, const uint8_t *key,
+ size_t key_len) {
+ for (size_t i = 0; i + key_len <= in_len; i += (size_t)(in[i] + 1)) {
+ if (memcmp(&in[i], key, key_len) == 0) {
+ *pout = (const uint8_t *)(&in[i + 1]);
+ *pout_len = in[i];
+ return (true);
+ }
+ }
+ return (false);
+}
+
+/* dot prepended by its length (3 bytes) */
+#define DOT_PROTO_ALPN "\x3" ISC_TLS_DOT_PROTO_ALPN_ID
+#define DOT_PROTO_ALPN_LEN (sizeof(DOT_PROTO_ALPN) - 1)
+
+static bool
+dot_select_next_protocol(const uint8_t **pout, uint8_t *pout_len,
+ const uint8_t *in, size_t in_len) {
+ return (protoneg_check_protocol(pout, pout_len, in, in_len,
+ (const uint8_t *)DOT_PROTO_ALPN,
+ DOT_PROTO_ALPN_LEN));
+}
+
+void
+isc_tlsctx_enable_dot_client_alpn(isc_tlsctx_t *ctx) {
+ REQUIRE(ctx != NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_CTX_set_alpn_protos(ctx, (const uint8_t *)DOT_PROTO_ALPN,
+ DOT_PROTO_ALPN_LEN);
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+static int
+dot_alpn_select_proto_cb(SSL *ssl, const unsigned char **out,
+ unsigned char *outlen, const unsigned char *in,
+ unsigned int inlen, void *arg) {
+ bool ret;
+
+ UNUSED(ssl);
+ UNUSED(arg);
+
+ ret = dot_select_next_protocol(out, outlen, in, inlen);
+
+ if (!ret) {
+ return (SSL_TLSEXT_ERR_NOACK);
+ }
+
+ return (SSL_TLSEXT_ERR_OK);
+}
+#endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
+
+void
+isc_tlsctx_enable_dot_server_alpn(isc_tlsctx_t *tls) {
+ REQUIRE(tls != NULL);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ SSL_CTX_set_alpn_select_cb(tls, dot_alpn_select_proto_cb, NULL);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
+}
+
+isc_result_t
+isc_tlsctx_enable_peer_verification(isc_tlsctx_t *tlsctx, const bool is_server,
+ isc_tls_cert_store_t *store,
+ const char *hostname,
+ bool hostname_ignore_subject) {
+ int ret = 0;
+ REQUIRE(tlsctx != NULL);
+ REQUIRE(store != NULL);
+
+ /* Set the hostname/IP address. */
+ if (!is_server && hostname != NULL && *hostname != '\0') {
+ struct in6_addr sa6;
+ struct in_addr sa;
+ X509_VERIFY_PARAM *param = SSL_CTX_get0_param(tlsctx);
+ unsigned int hostflags = X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS;
+
+ /* It might be an IP address. */
+ if (inet_pton(AF_INET6, hostname, &sa6) == 1 ||
+ inet_pton(AF_INET, hostname, &sa) == 1)
+ {
+ ret = X509_VERIFY_PARAM_set1_ip_asc(param, hostname);
+ } else {
+ /* It seems that it is a host name. Let's set it. */
+ ret = X509_VERIFY_PARAM_set1_host(param, hostname, 0);
+ }
+ if (ret != 1) {
+ ERR_clear_error();
+ return (ISC_R_FAILURE);
+ }
+
+#ifdef X509_CHECK_FLAG_NEVER_CHECK_SUBJECT
+ /*
+ * According to the RFC 8310, Section 8.1, Subject field MUST
+ * NOT be inspected when verifying a hostname when using
+ * DoT. Only SubjectAltName must be checked instead. That is
+ * not the case for HTTPS, though.
+ *
+ * Unfortunately, some quite old versions of OpenSSL (< 1.1.1)
+ * might lack the functionality to implement that. It should
+ * have very little real-world consequences, as most of the
+ * production-ready certificates issued by real CAs will have
+ * SubjectAltName set. In such a case, the Subject field is
+ * ignored.
+ */
+ if (hostname_ignore_subject) {
+ hostflags |= X509_CHECK_FLAG_NEVER_CHECK_SUBJECT;
+ }
+#else
+ UNUSED(hostname_ignore_subject);
+#endif
+ X509_VERIFY_PARAM_set_hostflags(param, hostflags);
+ }
+
+ /* "Attach" the cert store to the context */
+ SSL_CTX_set1_cert_store(tlsctx, store);
+
+ /* enable verification */
+ if (is_server) {
+ SSL_CTX_set_verify(tlsctx,
+ SSL_VERIFY_PEER |
+ SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ NULL);
+ } else {
+ SSL_CTX_set_verify(tlsctx, SSL_VERIFY_PEER, NULL);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_tlsctx_load_client_ca_names(isc_tlsctx_t *ctx, const char *ca_bundle_file) {
+ STACK_OF(X509_NAME) * cert_names;
+ REQUIRE(ctx != NULL);
+ REQUIRE(ca_bundle_file != NULL);
+
+ cert_names = SSL_load_client_CA_file(ca_bundle_file);
+ if (cert_names == NULL) {
+ ERR_clear_error();
+ return (ISC_R_FAILURE);
+ }
+
+ SSL_CTX_set_client_CA_list(ctx, cert_names);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_tls_cert_store_create(const char *ca_bundle_filename,
+ isc_tls_cert_store_t **pstore) {
+ int ret = 0;
+ isc_tls_cert_store_t *store = NULL;
+ REQUIRE(pstore != NULL && *pstore == NULL);
+
+ store = X509_STORE_new();
+ if (store == NULL) {
+ goto error;
+ }
+
+ /* Let's treat empty string as the default (system wide) store */
+ if (ca_bundle_filename != NULL && *ca_bundle_filename == '\0') {
+ ca_bundle_filename = NULL;
+ }
+
+ if (ca_bundle_filename == NULL) {
+ ret = X509_STORE_set_default_paths(store);
+ } else {
+ ret = X509_STORE_load_locations(store, ca_bundle_filename,
+ NULL);
+ }
+
+ if (ret == 0) {
+ goto error;
+ }
+
+ *pstore = store;
+ return (ISC_R_SUCCESS);
+
+error:
+ ERR_clear_error();
+ if (store != NULL) {
+ X509_STORE_free(store);
+ }
+ return (ISC_R_FAILURE);
+}
+
+void
+isc_tls_cert_store_free(isc_tls_cert_store_t **pstore) {
+ isc_tls_cert_store_t *store;
+ REQUIRE(pstore != NULL && *pstore != NULL);
+
+ store = *pstore;
+
+ X509_STORE_free(store);
+
+ *pstore = NULL;
+}
+
+#define TLSCTX_CACHE_MAGIC ISC_MAGIC('T', 'l', 'S', 'c')
+#define VALID_TLSCTX_CACHE(t) ISC_MAGIC_VALID(t, TLSCTX_CACHE_MAGIC)
+
+#define TLSCTX_CLIENT_SESSION_CACHE_MAGIC ISC_MAGIC('T', 'l', 'C', 'c')
+#define VALID_TLSCTX_CLIENT_SESSION_CACHE(t) \
+ ISC_MAGIC_VALID(t, TLSCTX_CLIENT_SESSION_CACHE_MAGIC)
+
+typedef struct isc_tlsctx_cache_entry {
+ /*
+ * We need a TLS context entry for each transport on both IPv4 and
+ * IPv6 in order to avoid cluttering a context-specific
+ * session-resumption cache.
+ */
+ isc_tlsctx_t *ctx[isc_tlsctx_cache_count - 1][2];
+ isc_tlsctx_client_session_cache_t
+ *client_sess_cache[isc_tlsctx_cache_count - 1][2];
+ /*
+ * One certificate store is enough for all the contexts defined
+ * above. We need that for peer validation.
+ */
+ isc_tls_cert_store_t *ca_store;
+} isc_tlsctx_cache_entry_t;
+
+struct isc_tlsctx_cache {
+ uint32_t magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+
+ isc_rwlock_t rwlock;
+ isc_ht_t *data;
+};
+
+void
+isc_tlsctx_cache_create(isc_mem_t *mctx, isc_tlsctx_cache_t **cachep) {
+ isc_tlsctx_cache_t *nc;
+
+ REQUIRE(cachep != NULL && *cachep == NULL);
+ nc = isc_mem_get(mctx, sizeof(*nc));
+
+ *nc = (isc_tlsctx_cache_t){ .magic = TLSCTX_CACHE_MAGIC };
+ isc_refcount_init(&nc->references, 1);
+ isc_mem_attach(mctx, &nc->mctx);
+
+ isc_ht_init(&nc->data, mctx, 5, ISC_HT_CASE_SENSITIVE);
+ isc_rwlock_init(&nc->rwlock, 0, 0);
+
+ *cachep = nc;
+}
+
+void
+isc_tlsctx_cache_attach(isc_tlsctx_cache_t *source,
+ isc_tlsctx_cache_t **targetp) {
+ REQUIRE(VALID_TLSCTX_CACHE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+tlsctx_cache_entry_destroy(isc_mem_t *mctx, isc_tlsctx_cache_entry_t *entry) {
+ size_t i, k;
+
+ for (i = 0; i < (isc_tlsctx_cache_count - 1); i++) {
+ for (k = 0; k < 2; k++) {
+ if (entry->ctx[i][k] != NULL) {
+ isc_tlsctx_free(&entry->ctx[i][k]);
+ }
+
+ if (entry->client_sess_cache[i][k] != NULL) {
+ isc_tlsctx_client_session_cache_detach(
+ &entry->client_sess_cache[i][k]);
+ }
+ }
+ }
+ if (entry->ca_store != NULL) {
+ isc_tls_cert_store_free(&entry->ca_store);
+ }
+ isc_mem_put(mctx, entry, sizeof(*entry));
+}
+
+static void
+tlsctx_cache_destroy(isc_tlsctx_cache_t *cache) {
+ isc_ht_iter_t *it = NULL;
+ isc_result_t result;
+
+ cache->magic = 0;
+
+ isc_refcount_destroy(&cache->references);
+
+ isc_ht_iter_create(cache->data, &it);
+ for (result = isc_ht_iter_first(it); result == ISC_R_SUCCESS;
+ result = isc_ht_iter_delcurrent_next(it))
+ {
+ isc_tlsctx_cache_entry_t *entry = NULL;
+ isc_ht_iter_current(it, (void **)&entry);
+ tlsctx_cache_entry_destroy(cache->mctx, entry);
+ }
+
+ isc_ht_iter_destroy(&it);
+ isc_ht_destroy(&cache->data);
+ isc_rwlock_destroy(&cache->rwlock);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+}
+
+void
+isc_tlsctx_cache_detach(isc_tlsctx_cache_t **cachep) {
+ isc_tlsctx_cache_t *cache = NULL;
+
+ REQUIRE(cachep != NULL);
+
+ cache = *cachep;
+ *cachep = NULL;
+
+ REQUIRE(VALID_TLSCTX_CACHE(cache));
+
+ if (isc_refcount_decrement(&cache->references) == 1) {
+ tlsctx_cache_destroy(cache);
+ }
+}
+
+isc_result_t
+isc_tlsctx_cache_add(
+ isc_tlsctx_cache_t *cache, const char *name,
+ const isc_tlsctx_cache_transport_t transport, const uint16_t family,
+ isc_tlsctx_t *ctx, isc_tls_cert_store_t *store,
+ isc_tlsctx_client_session_cache_t *client_sess_cache,
+ isc_tlsctx_t **pfound, isc_tls_cert_store_t **pfound_store,
+ isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) {
+ isc_result_t result = ISC_R_FAILURE;
+ size_t name_len, tr_offset;
+ isc_tlsctx_cache_entry_t *entry = NULL;
+ bool ipv6;
+
+ REQUIRE(VALID_TLSCTX_CACHE(cache));
+ REQUIRE(client_sess_cache == NULL ||
+ VALID_TLSCTX_CLIENT_SESSION_CACHE(client_sess_cache));
+ REQUIRE(name != NULL && *name != '\0');
+ REQUIRE(transport > isc_tlsctx_cache_none &&
+ transport < isc_tlsctx_cache_count);
+ REQUIRE(family == AF_INET || family == AF_INET6);
+ REQUIRE(ctx != NULL);
+
+ tr_offset = (transport - 1);
+ ipv6 = (family == AF_INET6);
+
+ RWLOCK(&cache->rwlock, isc_rwlocktype_write);
+
+ name_len = strlen(name);
+ result = isc_ht_find(cache->data, (const uint8_t *)name, name_len,
+ (void **)&entry);
+ if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
+ isc_tlsctx_client_session_cache_t *found_client_sess_cache;
+ /* The entry exists. */
+ if (pfound != NULL) {
+ INSIST(*pfound == NULL);
+ *pfound = entry->ctx[tr_offset][ipv6];
+ }
+
+ if (pfound_store != NULL && entry->ca_store != NULL) {
+ INSIST(*pfound_store == NULL);
+ *pfound_store = entry->ca_store;
+ }
+
+ found_client_sess_cache =
+ entry->client_sess_cache[tr_offset][ipv6];
+ if (pfound_client_sess_cache != NULL &&
+ found_client_sess_cache != NULL)
+ {
+ INSIST(*pfound_client_sess_cache == NULL);
+ *pfound_client_sess_cache = found_client_sess_cache;
+ }
+ result = ISC_R_EXISTS;
+ } else if (result == ISC_R_SUCCESS &&
+ entry->ctx[tr_offset][ipv6] == NULL)
+ {
+ /*
+ * The hash table entry exists, but is not filled for this
+ * particular transport/IP type combination.
+ */
+ entry->ctx[tr_offset][ipv6] = ctx;
+ entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache;
+ /*
+ * As the passed certificates store object is supposed
+ * to be internally managed by the cache object anyway,
+ * we might destroy the unneeded store object right now.
+ */
+ if (store != NULL && store != entry->ca_store) {
+ isc_tls_cert_store_free(&store);
+ }
+ result = ISC_R_SUCCESS;
+ } else {
+ /*
+ * The hash table entry does not exist, let's create one.
+ */
+ INSIST(result != ISC_R_SUCCESS);
+ entry = isc_mem_get(cache->mctx, sizeof(*entry));
+ /* Oracle/Red Hat Linux, GCC bug #53119 */
+ memset(entry, 0, sizeof(*entry));
+ entry->ctx[tr_offset][ipv6] = ctx;
+ entry->client_sess_cache[tr_offset][ipv6] = client_sess_cache;
+ entry->ca_store = store;
+ RUNTIME_CHECK(isc_ht_add(cache->data, (const uint8_t *)name,
+ name_len,
+ (void *)entry) == ISC_R_SUCCESS);
+ result = ISC_R_SUCCESS;
+ }
+
+ RWUNLOCK(&cache->rwlock, isc_rwlocktype_write);
+
+ return (result);
+}
+
+isc_result_t
+isc_tlsctx_cache_find(
+ isc_tlsctx_cache_t *cache, const char *name,
+ const isc_tlsctx_cache_transport_t transport, const uint16_t family,
+ isc_tlsctx_t **pctx, isc_tls_cert_store_t **pstore,
+ isc_tlsctx_client_session_cache_t **pfound_client_sess_cache) {
+ isc_result_t result = ISC_R_FAILURE;
+ size_t tr_offset;
+ isc_tlsctx_cache_entry_t *entry = NULL;
+ bool ipv6;
+
+ REQUIRE(VALID_TLSCTX_CACHE(cache));
+ REQUIRE(name != NULL && *name != '\0');
+ REQUIRE(transport > isc_tlsctx_cache_none &&
+ transport < isc_tlsctx_cache_count);
+ REQUIRE(family == AF_INET || family == AF_INET6);
+ REQUIRE(pctx != NULL && *pctx == NULL);
+
+ tr_offset = (transport - 1);
+ ipv6 = (family == AF_INET6);
+
+ RWLOCK(&cache->rwlock, isc_rwlocktype_read);
+
+ result = isc_ht_find(cache->data, (const uint8_t *)name, strlen(name),
+ (void **)&entry);
+
+ if (result == ISC_R_SUCCESS && pstore != NULL &&
+ entry->ca_store != NULL)
+ {
+ *pstore = entry->ca_store;
+ }
+
+ if (result == ISC_R_SUCCESS && entry->ctx[tr_offset][ipv6] != NULL) {
+ isc_tlsctx_client_session_cache_t *found_client_sess_cache =
+ entry->client_sess_cache[tr_offset][ipv6];
+
+ *pctx = entry->ctx[tr_offset][ipv6];
+
+ if (pfound_client_sess_cache != NULL &&
+ found_client_sess_cache != NULL)
+ {
+ INSIST(*pfound_client_sess_cache == NULL);
+ *pfound_client_sess_cache = found_client_sess_cache;
+ }
+ } else if (result == ISC_R_SUCCESS &&
+ entry->ctx[tr_offset][ipv6] == NULL)
+ {
+ result = ISC_R_NOTFOUND;
+ } else {
+ INSIST(result != ISC_R_SUCCESS);
+ }
+
+ RWUNLOCK(&cache->rwlock, isc_rwlocktype_read);
+
+ return (result);
+}
+
+typedef struct client_session_cache_entry client_session_cache_entry_t;
+
+typedef struct client_session_cache_bucket {
+ char *bucket_key;
+ size_t bucket_key_len;
+ /* Cache entries within the bucket (from the oldest to the newest). */
+ ISC_LIST(client_session_cache_entry_t) entries;
+} client_session_cache_bucket_t;
+
+struct client_session_cache_entry {
+ SSL_SESSION *session;
+ client_session_cache_bucket_t *bucket; /* "Parent" bucket pointer. */
+ ISC_LINK(client_session_cache_entry_t) bucket_link;
+ ISC_LINK(client_session_cache_entry_t) cache_link;
+};
+
+struct isc_tlsctx_client_session_cache {
+ uint32_t magic;
+ isc_refcount_t references;
+ isc_mem_t *mctx;
+
+ /*
+ * We need to keep a reference to the related TLS context in order
+ * to ensure that it remains valid while the TLS client sessions
+ * cache object is valid, as every TLS session object
+ * (SSL_SESSION) is "tied" to a particular context.
+ */
+ isc_tlsctx_t *ctx;
+
+ /*
+ * The idea is to have one bucket per remote server. Each bucket,
+ * can maintain multiple TLS sessions to that server, as BIND
+ * might want to establish multiple TLS connections to the remote
+ * server at once.
+ */
+ isc_ht_t *buckets;
+
+ /*
+ * The list of all current entries within the cache maintained in
+ * LRU-manner, so that the oldest entry might be efficiently
+ * removed.
+ */
+ ISC_LIST(client_session_cache_entry_t) lru_entries;
+ /* Number of the entries within the cache. */
+ size_t nentries;
+ /* Maximum number of the entries within the cache. */
+ size_t max_entries;
+
+ isc_mutex_t lock;
+};
+
+void
+isc_tlsctx_client_session_cache_create(
+ isc_mem_t *mctx, isc_tlsctx_t *ctx, const size_t max_entries,
+ isc_tlsctx_client_session_cache_t **cachep) {
+ isc_tlsctx_client_session_cache_t *nc;
+
+ REQUIRE(ctx != NULL);
+ REQUIRE(max_entries > 0);
+ REQUIRE(cachep != NULL && *cachep == NULL);
+
+ nc = isc_mem_get(mctx, sizeof(*nc));
+
+ *nc = (isc_tlsctx_client_session_cache_t){ .max_entries = max_entries };
+ isc_refcount_init(&nc->references, 1);
+ isc_mem_attach(mctx, &nc->mctx);
+ isc_tlsctx_attach(ctx, &nc->ctx);
+
+ isc_ht_init(&nc->buckets, mctx, 5, ISC_HT_CASE_SENSITIVE);
+ ISC_LIST_INIT(nc->lru_entries);
+ isc_mutex_init(&nc->lock);
+
+ nc->magic = TLSCTX_CLIENT_SESSION_CACHE_MAGIC;
+
+ *cachep = nc;
+}
+
+void
+isc_tlsctx_client_session_cache_attach(
+ isc_tlsctx_client_session_cache_t *source,
+ isc_tlsctx_client_session_cache_t **targetp) {
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ isc_refcount_increment(&source->references);
+
+ *targetp = source;
+}
+
+static void
+client_cache_entry_delete(isc_tlsctx_client_session_cache_t *restrict cache,
+ client_session_cache_entry_t *restrict entry) {
+ client_session_cache_bucket_t *restrict bucket = entry->bucket;
+
+ /* Unlink and free the cache entry */
+ ISC_LIST_UNLINK(bucket->entries, entry, bucket_link);
+ ISC_LIST_UNLINK(cache->lru_entries, entry, cache_link);
+ cache->nentries--;
+ (void)SSL_SESSION_free(entry->session);
+ isc_mem_put(cache->mctx, entry, sizeof(*entry));
+
+ /* The bucket is empty - let's remove it */
+ if (ISC_LIST_EMPTY(bucket->entries)) {
+ RUNTIME_CHECK(isc_ht_delete(cache->buckets,
+ (const uint8_t *)bucket->bucket_key,
+ bucket->bucket_key_len) ==
+ ISC_R_SUCCESS);
+
+ isc_mem_free(cache->mctx, bucket->bucket_key);
+ isc_mem_put(cache->mctx, bucket, sizeof(*bucket));
+ }
+}
+
+void
+isc_tlsctx_client_session_cache_detach(
+ isc_tlsctx_client_session_cache_t **cachep) {
+ isc_tlsctx_client_session_cache_t *cache = NULL;
+ client_session_cache_entry_t *entry = NULL, *next = NULL;
+
+ REQUIRE(cachep != NULL);
+
+ cache = *cachep;
+ *cachep = NULL;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+
+ if (isc_refcount_decrement(&cache->references) != 1) {
+ return;
+ }
+
+ cache->magic = 0;
+
+ isc_refcount_destroy(&cache->references);
+
+ entry = ISC_LIST_HEAD(cache->lru_entries);
+ while (entry != NULL) {
+ next = ISC_LIST_NEXT(entry, cache_link);
+ client_cache_entry_delete(cache, entry);
+ entry = next;
+ }
+
+ RUNTIME_CHECK(isc_ht_count(cache->buckets) == 0);
+ isc_ht_destroy(&cache->buckets);
+
+ isc_mutex_destroy(&cache->lock);
+ isc_tlsctx_free(&cache->ctx);
+ isc_mem_putanddetach(&cache->mctx, cache, sizeof(*cache));
+}
+
+static bool
+ssl_session_seems_resumable(const SSL_SESSION *sess) {
+#ifdef HAVE_SSL_SESSION_IS_RESUMABLE
+ /*
+ * If SSL_SESSION_is_resumable() is available, let's use that. It
+ * is expected to be available on OpenSSL >= 1.1.1 and its modern
+ * siblings.
+ */
+ return (SSL_SESSION_is_resumable(sess) != 0);
+#elif (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+ /*
+ * Taking into consideration that OpenSSL 1.1.0 uses opaque
+ * pointers for SSL_SESSION, we cannot implement a replacement for
+ * SSL_SESSION_is_resumable() manually. Let's use a sensible
+ * approximation for that, then: if there is an associated session
+ * ticket or session ID, then, most likely, the session is
+ * resumable.
+ */
+ unsigned int session_id_len = 0;
+ (void)SSL_SESSION_get_id(sess, &session_id_len);
+ return (SSL_SESSION_has_ticket(sess) || session_id_len > 0);
+#else
+ return (!sess->not_resumable &&
+ (sess->session_id_length > 0 || sess->tlsext_ticklen > 0));
+#endif
+}
+
+void
+isc_tlsctx_client_session_cache_keep(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls) {
+ size_t name_len;
+ isc_result_t result;
+ SSL_SESSION *sess;
+ client_session_cache_bucket_t *restrict bucket = NULL;
+ client_session_cache_entry_t *restrict entry = NULL;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
+ REQUIRE(tls != NULL);
+
+ sess = SSL_get1_session(tls);
+ if (sess == NULL) {
+ ERR_clear_error();
+ return;
+ } else if (!ssl_session_seems_resumable(sess)) {
+ SSL_SESSION_free(sess);
+ return;
+ }
+
+ isc_mutex_lock(&cache->lock);
+
+ name_len = strlen(remote_peer_name);
+ result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
+ name_len, (void **)&bucket);
+
+ if (result != ISC_R_SUCCESS) {
+ /* Let's create a new bucket */
+ INSIST(bucket == NULL);
+ bucket = isc_mem_get(cache->mctx, sizeof(*bucket));
+ *bucket = (client_session_cache_bucket_t){
+ .bucket_key = isc_mem_strdup(cache->mctx,
+ remote_peer_name),
+ .bucket_key_len = name_len
+ };
+ ISC_LIST_INIT(bucket->entries);
+ RUNTIME_CHECK(isc_ht_add(cache->buckets,
+ (const uint8_t *)remote_peer_name,
+ name_len,
+ (void *)bucket) == ISC_R_SUCCESS);
+ }
+
+ /* Let's add a new cache entry to the new/found bucket */
+ entry = isc_mem_get(cache->mctx, sizeof(*entry));
+ *entry = (client_session_cache_entry_t){ .session = sess,
+ .bucket = bucket };
+ ISC_LINK_INIT(entry, bucket_link);
+ ISC_LINK_INIT(entry, cache_link);
+
+ ISC_LIST_APPEND(bucket->entries, entry, bucket_link);
+
+ ISC_LIST_APPEND(cache->lru_entries, entry, cache_link);
+ cache->nentries++;
+
+ if (cache->nentries > cache->max_entries) {
+ /*
+ * Cache overrun. We need to remove the oldest entry from the
+ * cache
+ */
+ client_session_cache_entry_t *restrict oldest;
+ INSIST((cache->nentries - 1) == cache->max_entries);
+
+ oldest = ISC_LIST_HEAD(cache->lru_entries);
+ client_cache_entry_delete(cache, oldest);
+ }
+
+ isc_mutex_unlock(&cache->lock);
+}
+
+void
+isc_tlsctx_client_session_cache_reuse(isc_tlsctx_client_session_cache_t *cache,
+ char *remote_peer_name, isc_tls_t *tls) {
+ client_session_cache_bucket_t *restrict bucket = NULL;
+ client_session_cache_entry_t *restrict entry;
+ size_t name_len;
+ isc_result_t result;
+
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ REQUIRE(remote_peer_name != NULL && *remote_peer_name != '\0');
+ REQUIRE(tls != NULL);
+
+ isc_mutex_lock(&cache->lock);
+
+ /* Let's find the bucket */
+ name_len = strlen(remote_peer_name);
+ result = isc_ht_find(cache->buckets, (const uint8_t *)remote_peer_name,
+ name_len, (void **)&bucket);
+
+ if (result != ISC_R_SUCCESS) {
+ goto exit;
+ }
+
+ INSIST(bucket != NULL);
+
+ /*
+ * If the bucket has been found, let's use the newest session from
+ * the bucket, as it has the highest chance to be successfully
+ * resumed.
+ */
+ INSIST(!ISC_LIST_EMPTY(bucket->entries));
+ entry = ISC_LIST_TAIL(bucket->entries);
+ RUNTIME_CHECK(SSL_set_session(tls, entry->session) == 1);
+ client_cache_entry_delete(cache, entry);
+
+exit:
+ isc_mutex_unlock(&cache->lock);
+}
+
+void
+isc_tlsctx_client_session_cache_keep_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls) {
+ char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+
+ REQUIRE(remote_peer != NULL);
+
+ isc_sockaddr_format(remote_peer, peername, sizeof(peername));
+
+ isc_tlsctx_client_session_cache_keep(cache, peername, tls);
+}
+
+void
+isc_tlsctx_client_session_cache_reuse_sockaddr(
+ isc_tlsctx_client_session_cache_t *cache, isc_sockaddr_t *remote_peer,
+ isc_tls_t *tls) {
+ char peername[ISC_SOCKADDR_FORMATSIZE] = { 0 };
+
+ REQUIRE(remote_peer != NULL);
+
+ isc_sockaddr_format(remote_peer, peername, sizeof(peername));
+
+ isc_tlsctx_client_session_cache_reuse(cache, peername, tls);
+}
+
+const isc_tlsctx_t *
+isc_tlsctx_client_session_cache_getctx(
+ isc_tlsctx_client_session_cache_t *cache) {
+ REQUIRE(VALID_TLSCTX_CLIENT_SESSION_CACHE(cache));
+ return (cache->ctx);
+}
+
+void
+isc_tlsctx_set_random_session_id_context(isc_tlsctx_t *ctx) {
+ uint8_t session_id_ctx[SSL_MAX_SID_CTX_LENGTH] = { 0 };
+ const size_t len = ISC_MIN(20, sizeof(session_id_ctx));
+
+ REQUIRE(ctx != NULL);
+
+ RUNTIME_CHECK(RAND_bytes(session_id_ctx, len) == 1);
+
+ RUNTIME_CHECK(
+ SSL_CTX_set_session_id_context(ctx, session_id_ctx, len) == 1);
+}
diff --git a/lib/isc/tls_p.h b/lib/isc/tls_p.h
new file mode 100644
index 0000000..c0bd693
--- /dev/null
+++ b/lib/isc/tls_p.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
+
+void
+isc__tls_initialize(void);
+
+void
+isc__tls_shutdown(void);
diff --git a/lib/isc/tm.c b/lib/isc/tm.c
new file mode 100644
index 0000000..19a7bee
--- /dev/null
+++ b/lib/isc/tm.c
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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) 1997, 1998 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code was contributed to The NetBSD Foundation by Klaus Klein.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <isc/tm.h>
+#include <isc/util.h>
+
+/*
+ * Portable conversion routines for struct tm, replacing
+ * timegm() and strptime(), which are not available on all
+ * platforms and don't always behave the same way when they
+ * are.
+ */
+
+/*
+ * We do not implement alternate representations. However, we always
+ * check whether a given modifier is allowed for a certain conversion.
+ */
+#define ALT_E 0x01
+#define ALT_O 0x02
+#define LEGAL_ALT(x) \
+ { \
+ if ((alt_format & ~(x)) != 0) \
+ return ((0)); \
+ }
+
+#ifndef TM_YEAR_BASE
+#define TM_YEAR_BASE 1900
+#endif /* ifndef TM_YEAR_BASE */
+
+static const char *day[7] = { "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday" };
+static const char *abday[7] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+static const char *mon[12] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+};
+static const char *abmon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+static const char *am_pm[2] = { "AM", "PM" };
+
+static int
+conv_num(const char **buf, int *dest, int llim, int ulim) {
+ int result = 0;
+
+ /* The limit also determines the number of valid digits. */
+ int rulim = ulim;
+
+ if (!isdigit((unsigned char)**buf)) {
+ return (0);
+ }
+
+ do {
+ result *= 10;
+ result += *(*buf)++ - '0';
+ rulim /= 10;
+ } while ((result * 10 <= ulim) && rulim && **buf >= '0' &&
+ **buf <= '9');
+
+ if (result < llim || result > ulim) {
+ return (0);
+ }
+
+ *dest = result;
+ return (1);
+}
+
+time_t
+isc_tm_timegm(struct tm *tm) {
+ time_t ret;
+ int i, yday = 0, leapday;
+ int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 };
+
+ leapday = ((((tm->tm_year + 1900) % 4) == 0 &&
+ ((tm->tm_year + 1900) % 100) != 0) ||
+ ((tm->tm_year + 1900) % 400) == 0)
+ ? 1
+ : 0;
+ mdays[1] += leapday;
+
+ yday = tm->tm_mday - 1;
+ for (i = 1; i <= tm->tm_mon; i++) {
+ yday += mdays[i - 1];
+ }
+ ret = tm->tm_sec + (60 * tm->tm_min) + (3600 * tm->tm_hour) +
+ (86400 *
+ (yday + ((tm->tm_year - 70) * 365) + ((tm->tm_year - 69) / 4) -
+ ((tm->tm_year - 1) / 100) + ((tm->tm_year + 299) / 400)));
+ return (ret);
+}
+
+char *
+isc_tm_strptime(const char *buf, const char *fmt, struct tm *tm) {
+ char c, *ret;
+ const char *bp;
+ size_t len = 0;
+ int alt_format, i, split_year = 0;
+
+ REQUIRE(buf != NULL);
+ REQUIRE(fmt != NULL);
+ REQUIRE(tm != NULL);
+
+ memset(tm, 0, sizeof(struct tm));
+
+ bp = buf;
+
+ while ((c = *fmt) != '\0') {
+ /* Clear `alternate' modifier prior to new conversion. */
+ alt_format = 0;
+
+ /* Eat up white-space. */
+ if (isspace((unsigned char)c)) {
+ while (isspace((unsigned char)*bp)) {
+ bp++;
+ }
+
+ fmt++;
+ continue;
+ }
+
+ if ((c = *fmt++) != '%') {
+ goto literal;
+ }
+
+ again:
+ switch (c = *fmt++) {
+ case '%': /* "%%" is converted to "%". */
+ literal:
+ if (c != *bp++) {
+ return (0);
+ }
+ break;
+
+ /*
+ * "Alternative" modifiers. Just set the appropriate flag
+ * and start over again.
+ */
+ case 'E': /* "%E?" alternative conversion modifier. */
+ LEGAL_ALT(0);
+ alt_format |= ALT_E;
+ goto again;
+
+ case 'O': /* "%O?" alternative conversion modifier. */
+ LEGAL_ALT(0);
+ alt_format |= ALT_O;
+ goto again;
+
+ /*
+ * "Complex" conversion rules, implemented through recursion.
+ */
+ case 'c': /* Date and time, using the locale's format. */
+ LEGAL_ALT(ALT_E);
+ if (!(bp = isc_tm_strptime(bp, "%x %X", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'D': /* The date as "%m/%d/%y". */
+ LEGAL_ALT(0);
+ if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'R': /* The time as "%H:%M". */
+ LEGAL_ALT(0);
+ if (!(bp = isc_tm_strptime(bp, "%H:%M", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'r': /* The time in 12-hour clock representation. */
+ LEGAL_ALT(0);
+ if (!(bp = isc_tm_strptime(bp, "%I:%M:%S %p", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'T': /* The time as "%H:%M:%S". */
+ LEGAL_ALT(0);
+ if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'X': /* The time, using the locale's format. */
+ LEGAL_ALT(ALT_E);
+ if (!(bp = isc_tm_strptime(bp, "%H:%M:%S", tm))) {
+ return (0);
+ }
+ break;
+
+ case 'x': /* The date, using the locale's format. */
+ LEGAL_ALT(ALT_E);
+ if (!(bp = isc_tm_strptime(bp, "%m/%d/%y", tm))) {
+ return (0);
+ }
+ break;
+
+ /*
+ * "Elementary" conversion rules.
+ */
+ case 'A': /* The day of week, using the locale's form. */
+ case 'a':
+ LEGAL_ALT(0);
+ for (i = 0; i < 7; i++) {
+ /* Full name. */
+ len = strlen(day[i]);
+ if (strncasecmp(day[i], bp, len) == 0) {
+ break;
+ }
+
+ /* Abbreviated name. */
+ len = strlen(abday[i]);
+ if (strncasecmp(abday[i], bp, len) == 0) {
+ break;
+ }
+ }
+
+ /* Nothing matched. */
+ if (i == 7) {
+ return (0);
+ }
+
+ tm->tm_wday = i;
+ bp += len;
+ break;
+
+ case 'B': /* The month, using the locale's form. */
+ case 'b':
+ case 'h':
+ LEGAL_ALT(0);
+ for (i = 0; i < 12; i++) {
+ /* Full name. */
+ len = strlen(mon[i]);
+ if (strncasecmp(mon[i], bp, len) == 0) {
+ break;
+ }
+
+ /* Abbreviated name. */
+ len = strlen(abmon[i]);
+ if (strncasecmp(abmon[i], bp, len) == 0) {
+ break;
+ }
+ }
+
+ /* Nothing matched. */
+ if (i == 12) {
+ return (0);
+ }
+
+ tm->tm_mon = i;
+ bp += len;
+ break;
+
+ case 'C': /* The century number. */
+ LEGAL_ALT(ALT_E);
+ if (!(conv_num(&bp, &i, 0, 99))) {
+ return (0);
+ }
+
+ if (split_year) {
+ tm->tm_year = (tm->tm_year % 100) + (i * 100);
+ } else {
+ tm->tm_year = i * 100;
+ split_year = 1;
+ }
+ break;
+
+ case 'd': /* The day of month. */
+ case 'e':
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) {
+ return (0);
+ }
+ break;
+
+ case 'k': /* The hour (24-hour clock representation). */
+ LEGAL_ALT(0);
+ FALLTHROUGH;
+ case 'H':
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) {
+ return (0);
+ }
+ break;
+
+ case 'l': /* The hour (12-hour clock representation). */
+ LEGAL_ALT(0);
+ FALLTHROUGH;
+ case 'I':
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) {
+ return (0);
+ }
+ if (tm->tm_hour == 12) {
+ tm->tm_hour = 0;
+ }
+ break;
+
+ case 'j': /* The day of year. */
+ LEGAL_ALT(0);
+ if (!(conv_num(&bp, &i, 1, 366))) {
+ return (0);
+ }
+ tm->tm_yday = i - 1;
+ break;
+
+ case 'M': /* The minute. */
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_min, 0, 59))) {
+ return (0);
+ }
+ break;
+
+ case 'm': /* The month. */
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &i, 1, 12))) {
+ return (0);
+ }
+ tm->tm_mon = i - 1;
+ break;
+
+ case 'p': /* The locale's equivalent of AM/PM. */
+ LEGAL_ALT(0);
+ /* AM? */
+ if (strcasecmp(am_pm[0], bp) == 0) {
+ if (tm->tm_hour > 11) {
+ return (0);
+ }
+
+ bp += strlen(am_pm[0]);
+ break;
+ }
+ /* PM? */
+ else if (strcasecmp(am_pm[1], bp) == 0)
+ {
+ if (tm->tm_hour > 11) {
+ return (0);
+ }
+
+ tm->tm_hour += 12;
+ bp += strlen(am_pm[1]);
+ break;
+ }
+
+ /* Nothing matched. */
+ return (0);
+
+ case 'S': /* The seconds. */
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) {
+ return (0);
+ }
+ break;
+
+ case 'U': /* The week of year, beginning on sunday. */
+ case 'W': /* The week of year, beginning on monday. */
+ LEGAL_ALT(ALT_O);
+ /*
+ * XXX This is bogus, as we can not assume any valid
+ * information present in the tm structure at this
+ * point to calculate a real value, so just check the
+ * range for now.
+ */
+ if (!(conv_num(&bp, &i, 0, 53))) {
+ return (0);
+ }
+ break;
+
+ case 'w': /* The day of week, beginning on sunday. */
+ LEGAL_ALT(ALT_O);
+ if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) {
+ return (0);
+ }
+ break;
+
+ case 'Y': /* The year. */
+ LEGAL_ALT(ALT_E);
+ if (!(conv_num(&bp, &i, 0, 9999))) {
+ return (0);
+ }
+
+ tm->tm_year = i - TM_YEAR_BASE;
+ break;
+
+ case 'y': /* The year within 100 years of the epoch. */
+ LEGAL_ALT(ALT_E | ALT_O);
+ if (!(conv_num(&bp, &i, 0, 99))) {
+ return (0);
+ }
+
+ if (split_year) {
+ tm->tm_year = ((tm->tm_year / 100) * 100) + i;
+ break;
+ }
+ split_year = 1;
+ if (i <= 68) {
+ tm->tm_year = i + 2000 - TM_YEAR_BASE;
+ } else {
+ tm->tm_year = i + 1900 - TM_YEAR_BASE;
+ }
+ break;
+
+ /*
+ * Miscellaneous conversions.
+ */
+ case 'n': /* Any kind of white-space. */
+ case 't':
+ LEGAL_ALT(0);
+ while (isspace((unsigned char)*bp)) {
+ bp++;
+ }
+ break;
+
+ default: /* Unknown/unsupported conversion. */
+ return (0);
+ }
+ }
+
+ /* LINTED functional specification */
+ DE_CONST(bp, ret);
+ return (ret);
+}
diff --git a/lib/isc/trampoline.c b/lib/isc/trampoline.c
new file mode 100644
index 0000000..58171d9
--- /dev/null
+++ b/lib/isc/trampoline.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+#include <uv.h>
+
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include "trampoline_p.h"
+
+#define ISC__TRAMPOLINE_UNUSED 0
+
+struct isc__trampoline {
+ int tid; /* const */
+ uintptr_t self;
+ isc_threadfunc_t start;
+ isc_threadarg_t arg;
+ void *jemalloc_enforce_init;
+};
+
+/*
+ * We can't use isc_mem API here, because it's called too early and the
+ * isc_mem_debugging flags can be changed later causing mismatch between flags
+ * used for isc_mem_get() and isc_mem_put().
+ *
+ * Since this is a single allocation at library load and deallocation at library
+ * unload, using the standard allocator without the tracking is fine for this
+ * single purpose.
+ *
+ * We can't use isc_mutex API either, because we track whether the mutexes get
+ * properly destroyed, and we intentionally leak the static mutex here without
+ * destroying it to prevent data race between library destructor running while
+ * thread is being still created.
+ */
+
+static uv_mutex_t isc__trampoline_lock;
+static isc__trampoline_t **trampolines;
+thread_local size_t isc_tid_v = SIZE_MAX;
+static size_t isc__trampoline_min = 1;
+static size_t isc__trampoline_max = 65;
+
+static isc__trampoline_t *
+isc__trampoline_new(int tid, isc_threadfunc_t start, isc_threadarg_t arg) {
+ isc__trampoline_t *trampoline = calloc(1, sizeof(*trampoline));
+ RUNTIME_CHECK(trampoline != NULL);
+
+ *trampoline = (isc__trampoline_t){
+ .tid = tid,
+ .start = start,
+ .arg = arg,
+ .self = ISC__TRAMPOLINE_UNUSED,
+ };
+
+ return (trampoline);
+}
+
+void
+isc__trampoline_initialize(void) {
+ uv_mutex_init(&isc__trampoline_lock);
+
+ trampolines = calloc(isc__trampoline_max, sizeof(trampolines[0]));
+ RUNTIME_CHECK(trampolines != NULL);
+
+ /* Get the trampoline slot 0 for the main thread */
+ trampolines[0] = isc__trampoline_new(0, NULL, NULL);
+ isc_tid_v = trampolines[0]->tid;
+ trampolines[0]->self = isc_thread_self();
+
+ /* Initialize the other trampolines */
+ for (size_t i = 1; i < isc__trampoline_max; i++) {
+ trampolines[i] = NULL;
+ }
+ isc__trampoline_min = 1;
+}
+
+void
+isc__trampoline_shutdown(void) {
+ /*
+ * When the program using the library exits abruptly and the library
+ * gets unloaded, there might be some existing trampolines from unjoined
+ * threads. We intentionally ignore those and don't check whether all
+ * trampolines have been cleared before exiting, so we leak a little bit
+ * of resources here, including the lock.
+ */
+ free(trampolines[0]);
+}
+
+isc__trampoline_t *
+isc__trampoline_get(isc_threadfunc_t start, isc_threadarg_t arg) {
+ isc__trampoline_t **tmp = NULL;
+ isc__trampoline_t *trampoline = NULL;
+ uv_mutex_lock(&isc__trampoline_lock);
+again:
+ for (size_t i = isc__trampoline_min; i < isc__trampoline_max; i++) {
+ if (trampolines[i] == NULL) {
+ trampoline = isc__trampoline_new(i, start, arg);
+ trampolines[i] = trampoline;
+ isc__trampoline_min = i + 1;
+ goto done;
+ }
+ }
+ tmp = calloc(2 * isc__trampoline_max, sizeof(trampolines[0]));
+ RUNTIME_CHECK(tmp != NULL);
+ for (size_t i = 0; i < isc__trampoline_max; i++) {
+ tmp[i] = trampolines[i];
+ }
+ for (size_t i = isc__trampoline_max; i < 2 * isc__trampoline_max; i++) {
+ tmp[i] = NULL;
+ }
+ free(trampolines);
+ trampolines = tmp;
+ isc__trampoline_max = isc__trampoline_max * 2;
+ goto again;
+done:
+ INSIST(trampoline != NULL);
+ uv_mutex_unlock(&isc__trampoline_lock);
+
+ return (trampoline);
+}
+
+void
+isc__trampoline_detach(isc__trampoline_t *trampoline) {
+ uv_mutex_lock(&isc__trampoline_lock);
+ REQUIRE(trampoline->self == isc_thread_self());
+ REQUIRE(trampoline->tid > 0);
+ REQUIRE((size_t)trampoline->tid < isc__trampoline_max);
+ REQUIRE(trampolines[trampoline->tid] == trampoline);
+
+ trampolines[trampoline->tid] = NULL;
+
+ if (isc__trampoline_min > (size_t)trampoline->tid) {
+ isc__trampoline_min = trampoline->tid;
+ }
+
+ free(trampoline->jemalloc_enforce_init);
+ free(trampoline);
+
+ uv_mutex_unlock(&isc__trampoline_lock);
+ return;
+}
+
+void
+isc__trampoline_attach(isc__trampoline_t *trampoline) {
+ uv_mutex_lock(&isc__trampoline_lock);
+ REQUIRE(trampoline->self == ISC__TRAMPOLINE_UNUSED);
+ REQUIRE(trampoline->tid > 0);
+ REQUIRE((size_t)trampoline->tid < isc__trampoline_max);
+ REQUIRE(trampolines[trampoline->tid] == trampoline);
+
+ /* Initialize the trampoline */
+ isc_tid_v = trampoline->tid;
+ trampoline->self = isc_thread_self();
+
+ /*
+ * Ensure every thread starts with a malloc() call to prevent memory
+ * bloat caused by a jemalloc quirk. While this dummy allocation is
+ * not used for anything, free() must not be immediately called for it
+ * so that an optimizing compiler does not strip away such a pair of
+ * malloc() + free() calls altogether, as it would foil the fix.
+ */
+ trampoline->jemalloc_enforce_init = malloc(8);
+ uv_mutex_unlock(&isc__trampoline_lock);
+}
+
+isc_threadresult_t
+isc__trampoline_run(isc_threadarg_t arg) {
+ isc__trampoline_t *trampoline = (isc__trampoline_t *)arg;
+ isc_threadresult_t result;
+
+ isc__trampoline_attach(trampoline);
+
+ /* Run the main function */
+ result = (trampoline->start)(trampoline->arg);
+
+ isc__trampoline_detach(trampoline);
+
+ return (result);
+}
diff --git a/lib/isc/trampoline_p.h b/lib/isc/trampoline_p.h
new file mode 100644
index 0000000..616a3dd
--- /dev/null
+++ b/lib/isc/trampoline_p.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
+
+#include <isc/thread.h>
+
+/*! \file isc/trampoline_p.h
+ * \brief isc__trampoline: allows safe reuse of thread ID numbers.
+ *
+ * The 'isc_hp' hazard pointer API uses an internal thread ID
+ * variable ('tid_v') that is incremented for each new thread that uses
+ * hazard pointers. This thread ID is then used as an index into a global
+ * shared table of hazard pointer state.
+ *
+ * Since the thread ID is only incremented and never decremented, the
+ * table can overflow if threads are frequently created and destroyed.
+ *
+ * A trampoline is a thin wrapper around any function to be called from
+ * a newly launched thread. It maintains a table of thread IDs used by
+ * current and previous threads; when a thread is destroyed, its slot in
+ * the trampoline table becomes available, and the next thread to occupy
+ * that slot can use the same thread ID that its predecessor did.
+ *
+ * The trampoline table initially has space for 64 worker threads in
+ * addition to the main thread. If more threads than that are in
+ * concurrent use, the table is reallocated with twice as much space.
+ * (Note that the number of concurrent threads is currently capped at
+ * 128 by the queue and hazard pointer implementations.)
+ */
+
+typedef struct isc__trampoline isc__trampoline_t;
+
+void
+isc__trampoline_initialize(void);
+/*%<
+ * Initialize the thread trampoline internal structures, must be called only
+ * once as a library constructor (see lib/isc/lib.c).
+ */
+
+void
+isc__trampoline_shutdown(void);
+/*%<
+ * Destroy the thread trampoline internal structures, must be called only
+ * once as a library destructor (see lib/isc/lib.c).
+ */
+
+isc__trampoline_t *
+isc__trampoline_get(isc_threadfunc_t start_routine, isc_threadarg_t arg);
+/*%<
+ * Get a free thread trampoline structure and initialize it with
+ * start_routine and arg passed to start_routine.
+ *
+ * Requires:
+ *\li 'start_routine' is a valid non-NULL thread start_routine
+ */
+
+void
+isc__trampoline_attach(isc__trampoline_t *trampoline);
+void
+isc__trampoline_detach(isc__trampoline_t *trampoline);
+/*%<
+ * Attach/detach the trampoline to/from the current thread.
+ *
+ * Requires:
+ * \li 'trampoline' is a valid isc__trampoline_t
+ */
+
+isc_threadresult_t
+isc__trampoline_run(isc_threadarg_t arg);
+/*%<
+ * Run the thread trampoline, this will get passed to the actual
+ * pthread_create(), initialize the isc_tid_v.
+ *
+ * Requires:
+ *\li 'arg' is a valid isc_trampoline_t
+ *
+ * Returns:
+ *\li return value from start_routine (see isc__trampoline_get())
+ */
diff --git a/lib/isc/url.c b/lib/isc/url.c
new file mode 100644
index 0000000..cccb712
--- /dev/null
+++ b/lib/isc/url.c
@@ -0,0 +1,671 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 and MIT
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 Joyent, Inc. and other Node contributors. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <isc/url.h>
+#include <isc/util.h>
+
+#ifndef BIT_AT
+#define BIT_AT(a, i) \
+ (!!((unsigned int)(a)[(unsigned int)(i) >> 3] & \
+ (1 << ((unsigned int)(i)&7))))
+#endif
+
+#if HTTP_PARSER_STRICT
+#define T(v) 0
+#else
+#define T(v) v
+#endif
+
+static const uint8_t normal_url_char[32] = {
+ /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+ /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
+ 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
+ /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+ /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
+ 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
+ /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
+ 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
+ /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+ /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
+ /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
+ 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
+};
+
+#undef T
+
+typedef enum {
+ s_dead = 1, /* important that this is > 0 */
+
+ s_start_req_or_res,
+ s_res_or_resp_H,
+ s_start_res,
+ s_res_H,
+ s_res_HT,
+ s_res_HTT,
+ s_res_HTTP,
+ s_res_http_major,
+ s_res_http_dot,
+ s_res_http_minor,
+ s_res_http_end,
+ s_res_first_status_code,
+ s_res_status_code,
+ s_res_status_start,
+ s_res_status,
+ s_res_line_almost_done,
+
+ s_start_req,
+
+ s_req_method,
+ s_req_spaces_before_url,
+ s_req_schema,
+ s_req_schema_slash,
+ s_req_schema_slash_slash,
+ s_req_server_start,
+ s_req_server,
+ s_req_server_with_at,
+ s_req_path,
+ s_req_query_string_start,
+ s_req_query_string,
+ s_req_fragment_start,
+ s_req_fragment,
+ s_req_http_start,
+ s_req_http_H,
+ s_req_http_HT,
+ s_req_http_HTT,
+ s_req_http_HTTP,
+ s_req_http_I,
+ s_req_http_IC,
+ s_req_http_major,
+ s_req_http_dot,
+ s_req_http_minor,
+ s_req_http_end,
+ s_req_line_almost_done,
+
+ s_header_field_start,
+ s_header_field,
+ s_header_value_discard_ws,
+ s_header_value_discard_ws_almost_done,
+ s_header_value_discard_lws,
+ s_header_value_start,
+ s_header_value,
+ s_header_value_lws,
+
+ s_header_almost_done,
+
+ s_chunk_size_start,
+ s_chunk_size,
+ s_chunk_parameters,
+ s_chunk_size_almost_done,
+
+ s_headers_almost_done,
+ s_headers_done,
+
+ /*
+ * Important: 's_headers_done' must be the last 'header' state. All
+ * states beyond this must be 'body' states. It is used for overflow
+ * checking. See the PARSING_HEADER() macro.
+ */
+
+ s_chunk_data,
+ s_chunk_data_almost_done,
+ s_chunk_data_done,
+
+ s_body_identity,
+ s_body_identity_eof,
+
+ s_message_done
+} state_t;
+
+typedef enum {
+ s_http_host_dead = 1,
+ s_http_userinfo_start,
+ s_http_userinfo,
+ s_http_host_start,
+ s_http_host_v6_start,
+ s_http_host,
+ s_http_host_v6,
+ s_http_host_v6_end,
+ s_http_host_v6_zone_start,
+ s_http_host_v6_zone,
+ s_http_host_port_start,
+ s_http_host_port
+} host_state_t;
+
+/* Macros for character classes; depends on strict-mode */
+#define IS_MARK(c) \
+ ((c) == '-' || (c) == '_' || (c) == '.' || (c) == '!' || (c) == '~' || \
+ (c) == '*' || (c) == '\'' || (c) == '(' || (c) == ')')
+#define IS_USERINFO_CHAR(c) \
+ (isalnum((unsigned char)c) || IS_MARK(c) || (c) == '%' || \
+ (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
+ (c) == '$' || (c) == ',')
+
+#if HTTP_PARSER_STRICT
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
+#define IS_HOST_CHAR(c) (isalnum((unsigned char)c) || (c) == '.' || (c) == '-')
+#else
+#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c) || ((c)&0x80))
+#define IS_HOST_CHAR(c) \
+ (isalnum((unsigned char)c) || (c) == '.' || (c) == '-' || (c) == '_')
+#endif
+
+/*
+ * Our URL parser.
+ *
+ * This is designed to be shared by http_parser_execute() for URL validation,
+ * hence it has a state transition + byte-for-byte interface. In addition, it
+ * is meant to be embedded in http_parser_parse_url(), which does the dirty
+ * work of turning state transitions URL components for its API.
+ *
+ * This function should only be invoked with non-space characters. It is
+ * assumed that the caller cares about (and can detect) the transition between
+ * URL and non-URL states by looking for these.
+ */
+static state_t
+parse_url_char(state_t s, const char ch) {
+ if (ch == ' ' || ch == '\r' || ch == '\n') {
+ return (s_dead);
+ }
+
+#if HTTP_PARSER_STRICT
+ if (ch == '\t' || ch == '\f') {
+ return (s_dead);
+ }
+#endif
+
+ switch (s) {
+ case s_req_spaces_before_url:
+ /* Proxied requests are followed by scheme of an absolute URI
+ * (alpha). All methods except CONNECT are followed by '/' or
+ * '*'.
+ */
+
+ if (ch == '/' || ch == '*') {
+ return (s_req_path);
+ }
+
+ if (isalpha((unsigned char)ch)) {
+ return (s_req_schema);
+ }
+
+ break;
+
+ case s_req_schema:
+ if (isalpha((unsigned char)ch)) {
+ return (s);
+ }
+
+ if (ch == ':') {
+ return (s_req_schema_slash);
+ }
+
+ break;
+
+ case s_req_schema_slash:
+ if (ch == '/') {
+ return (s_req_schema_slash_slash);
+ }
+
+ break;
+
+ case s_req_schema_slash_slash:
+ if (ch == '/') {
+ return (s_req_server_start);
+ }
+
+ break;
+
+ case s_req_server_with_at:
+ if (ch == '@') {
+ return (s_dead);
+ }
+
+ FALLTHROUGH;
+ case s_req_server_start:
+ case s_req_server:
+ if (ch == '/') {
+ return (s_req_path);
+ }
+
+ if (ch == '?') {
+ return (s_req_query_string_start);
+ }
+
+ if (ch == '@') {
+ return (s_req_server_with_at);
+ }
+
+ if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
+ return (s_req_server);
+ }
+
+ break;
+
+ case s_req_path:
+ if (IS_URL_CHAR(ch)) {
+ return (s);
+ }
+
+ switch (ch) {
+ case '?':
+ return (s_req_query_string_start);
+
+ case '#':
+ return (s_req_fragment_start);
+ }
+
+ break;
+
+ case s_req_query_string_start:
+ case s_req_query_string:
+ if (IS_URL_CHAR(ch)) {
+ return (s_req_query_string);
+ }
+
+ switch (ch) {
+ case '?':
+ /* allow extra '?' in query string */
+ return (s_req_query_string);
+
+ case '#':
+ return (s_req_fragment_start);
+ }
+
+ break;
+
+ case s_req_fragment_start:
+ if (IS_URL_CHAR(ch)) {
+ return (s_req_fragment);
+ }
+
+ switch (ch) {
+ case '?':
+ return (s_req_fragment);
+
+ case '#':
+ return (s);
+ }
+
+ break;
+
+ case s_req_fragment:
+ if (IS_URL_CHAR(ch)) {
+ return (s);
+ }
+
+ switch (ch) {
+ case '?':
+ case '#':
+ return (s);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ /*
+ * We should never fall out of the switch above unless there's an
+ * error.
+ */
+ return (s_dead);
+}
+
+static host_state_t
+http_parse_host_char(host_state_t s, const char ch) {
+ switch (s) {
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ if (ch == '@') {
+ return (s_http_host_start);
+ }
+
+ if (IS_USERINFO_CHAR(ch)) {
+ return (s_http_userinfo);
+ }
+ break;
+
+ case s_http_host_start:
+ if (ch == '[') {
+ return (s_http_host_v6_start);
+ }
+
+ if (IS_HOST_CHAR(ch)) {
+ return (s_http_host);
+ }
+
+ break;
+
+ case s_http_host:
+ if (IS_HOST_CHAR(ch)) {
+ return (s_http_host);
+ }
+
+ FALLTHROUGH;
+ case s_http_host_v6_end:
+ if (ch == ':') {
+ return (s_http_host_port_start);
+ }
+
+ break;
+
+ case s_http_host_v6:
+ if (ch == ']') {
+ return (s_http_host_v6_end);
+ }
+
+ FALLTHROUGH;
+ case s_http_host_v6_start:
+ if (isxdigit((unsigned char)ch) || ch == ':' || ch == '.') {
+ return (s_http_host_v6);
+ }
+
+ if (s == s_http_host_v6 && ch == '%') {
+ return (s_http_host_v6_zone_start);
+ }
+ break;
+
+ case s_http_host_v6_zone:
+ if (ch == ']') {
+ return (s_http_host_v6_end);
+ }
+
+ FALLTHROUGH;
+ case s_http_host_v6_zone_start:
+ /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */
+ if (isalnum((unsigned char)ch) || ch == '%' || ch == '.' ||
+ ch == '-' || ch == '_' || ch == '~')
+ {
+ return (s_http_host_v6_zone);
+ }
+ break;
+
+ case s_http_host_port:
+ case s_http_host_port_start:
+ if (isdigit((unsigned char)ch)) {
+ return (s_http_host_port);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ return (s_http_host_dead);
+}
+
+static isc_result_t
+http_parse_host(const char *buf, isc_url_parser_t *up, int found_at) {
+ host_state_t s;
+ const char *p = NULL;
+ size_t buflen = up->field_data[ISC_UF_HOST].off +
+ up->field_data[ISC_UF_HOST].len;
+
+ REQUIRE((up->field_set & (1 << ISC_UF_HOST)) != 0);
+
+ up->field_data[ISC_UF_HOST].len = 0;
+
+ s = found_at ? s_http_userinfo_start : s_http_host_start;
+
+ for (p = buf + up->field_data[ISC_UF_HOST].off; p < buf + buflen; p++) {
+ host_state_t new_s = http_parse_host_char(s, *p);
+
+ if (new_s == s_http_host_dead) {
+ return (ISC_R_FAILURE);
+ }
+
+ switch (new_s) {
+ case s_http_host:
+ if (s != s_http_host) {
+ up->field_data[ISC_UF_HOST].off =
+ (uint16_t)(p - buf);
+ }
+ up->field_data[ISC_UF_HOST].len++;
+ break;
+
+ case s_http_host_v6:
+ if (s != s_http_host_v6) {
+ up->field_data[ISC_UF_HOST].off =
+ (uint16_t)(p - buf);
+ }
+ up->field_data[ISC_UF_HOST].len++;
+ break;
+
+ case s_http_host_v6_zone_start:
+ case s_http_host_v6_zone:
+ up->field_data[ISC_UF_HOST].len++;
+ break;
+
+ case s_http_host_port:
+ if (s != s_http_host_port) {
+ up->field_data[ISC_UF_PORT].off =
+ (uint16_t)(p - buf);
+ up->field_data[ISC_UF_PORT].len = 0;
+ up->field_set |= (1 << ISC_UF_PORT);
+ }
+ up->field_data[ISC_UF_PORT].len++;
+ break;
+
+ case s_http_userinfo:
+ if (s != s_http_userinfo) {
+ up->field_data[ISC_UF_USERINFO].off =
+ (uint16_t)(p - buf);
+ up->field_data[ISC_UF_USERINFO].len = 0;
+ up->field_set |= (1 << ISC_UF_USERINFO);
+ }
+ up->field_data[ISC_UF_USERINFO].len++;
+ break;
+
+ default:
+ break;
+ }
+
+ s = new_s;
+ }
+
+ /* Make sure we don't end somewhere unexpected */
+ switch (s) {
+ case s_http_host_start:
+ case s_http_host_v6_start:
+ case s_http_host_v6:
+ case s_http_host_v6_zone_start:
+ case s_http_host_v6_zone:
+ case s_http_host_port_start:
+ case s_http_userinfo:
+ case s_http_userinfo_start:
+ return (ISC_R_FAILURE);
+ default:
+ break;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isc_url_parse(const char *buf, size_t buflen, bool is_connect,
+ isc_url_parser_t *up) {
+ state_t s;
+ isc_url_field_t uf, old_uf;
+ int found_at = 0;
+ const char *p = NULL;
+
+ if (buflen == 0) {
+ return (ISC_R_FAILURE);
+ }
+
+ up->port = up->field_set = 0;
+ s = is_connect ? s_req_server_start : s_req_spaces_before_url;
+ old_uf = ISC_UF_MAX;
+
+ for (p = buf; p < buf + buflen; p++) {
+ s = parse_url_char(s, *p);
+
+ /* Figure out the next field that we're operating on */
+ switch (s) {
+ case s_dead:
+ return (ISC_R_FAILURE);
+
+ /* Skip delimiters */
+ case s_req_schema_slash:
+ case s_req_schema_slash_slash:
+ case s_req_server_start:
+ case s_req_query_string_start:
+ case s_req_fragment_start:
+ continue;
+
+ case s_req_schema:
+ uf = ISC_UF_SCHEMA;
+ break;
+
+ case s_req_server_with_at:
+ found_at = 1;
+ FALLTHROUGH;
+ case s_req_server:
+ uf = ISC_UF_HOST;
+ break;
+
+ case s_req_path:
+ uf = ISC_UF_PATH;
+ break;
+
+ case s_req_query_string:
+ uf = ISC_UF_QUERY;
+ break;
+
+ case s_req_fragment:
+ uf = ISC_UF_FRAGMENT;
+ break;
+
+ default:
+ UNREACHABLE();
+ }
+
+ /* Nothing's changed; soldier on */
+ if (uf == old_uf) {
+ up->field_data[uf].len++;
+ continue;
+ }
+
+ up->field_data[uf].off = (uint16_t)(p - buf);
+ up->field_data[uf].len = 1;
+
+ up->field_set |= (1 << uf);
+ old_uf = uf;
+ }
+
+ /* host must be present if there is a schema */
+ /* parsing http:///toto will fail */
+ if ((up->field_set & (1 << ISC_UF_SCHEMA)) &&
+ (up->field_set & (1 << ISC_UF_HOST)) == 0)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ if (up->field_set & (1 << ISC_UF_HOST)) {
+ isc_result_t result;
+
+ result = http_parse_host(buf, up, found_at);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ /* CONNECT requests can only contain "hostname:port" */
+ if (is_connect &&
+ up->field_set != ((1 << ISC_UF_HOST) | (1 << ISC_UF_PORT)))
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ if (up->field_set & (1 << ISC_UF_PORT)) {
+ uint16_t off;
+ uint16_t len;
+ const char *pp = NULL;
+ const char *end = NULL;
+ unsigned long v;
+
+ off = up->field_data[ISC_UF_PORT].off;
+ len = up->field_data[ISC_UF_PORT].len;
+ end = buf + off + len;
+
+ /*
+ * NOTE: The characters are already validated and are in the
+ * [0-9] range
+ */
+ INSIST(off + len <= buflen);
+
+ v = 0;
+ for (pp = buf + off; pp < end; pp++) {
+ v *= 10;
+ v += *pp - '0';
+
+ /* Ports have a max value of 2^16 */
+ if (v > 0xffff) {
+ return (ISC_R_RANGE);
+ }
+ }
+
+ up->port = (uint16_t)v;
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isc/utf8.c b/lib/isc/utf8.c
new file mode 100644
index 0000000..a348c5d
--- /dev/null
+++ b/lib/isc/utf8.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <isc/utf8.h>
+#include <isc/util.h>
+
+/*
+ * UTF-8 is defined in "The Unicode Standard -- Version 4.0"
+ * Also see RFC 3629.
+ *
+ * Char. number range | UTF-8 octet sequence
+ * (hexadecimal) | (binary)
+ * --------------------+---------------------------------------------
+ * 0000 0000-0000 007F | 0xxxxxxx
+ * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
+ * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
+ * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+bool
+isc_utf8_valid(const unsigned char *buf, size_t len) {
+ REQUIRE(buf != NULL);
+
+ for (size_t i = 0; i < len; i++) {
+ if (buf[i] <= 0x7f) {
+ continue;
+ }
+ if ((i + 1) < len && (buf[i] & 0xe0) == 0xc0 &&
+ (buf[i + 1] & 0xc0) == 0x80)
+ {
+ unsigned int w;
+ w = (buf[i] & 0x1f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x80) {
+ return (false);
+ }
+ continue;
+ }
+ if ((i + 2) < len && (buf[i] & 0xf0) == 0xe0 &&
+ (buf[i + 1] & 0xc0) == 0x80 && (buf[i + 2] & 0xc0) == 0x80)
+ {
+ unsigned int w;
+ w = (buf[i] & 0x0f) << 12;
+ w |= (buf[++i] & 0x3f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x0800) {
+ return (false);
+ }
+ continue;
+ }
+ if ((i + 3) < len && (buf[i] & 0xf8) == 0xf0 &&
+ (buf[i + 1] & 0xc0) == 0x80 &&
+ (buf[i + 2] & 0xc0) == 0x80 && (buf[i + 3] & 0xc0) == 0x80)
+ {
+ unsigned int w;
+ w = (buf[i] & 0x07) << 18;
+ w |= (buf[++i] & 0x3f) << 12;
+ w |= (buf[++i] & 0x3f) << 6;
+ w |= (buf[++i] & 0x3f);
+ if (w < 0x10000 || w > 0x10FFFF) {
+ return (false);
+ }
+ continue;
+ }
+ return (false);
+ }
+ return (true);
+}
+
+bool
+isc_utf8_bom(const unsigned char *buf, size_t len) {
+ REQUIRE(buf != NULL);
+
+ if (len >= 3U && !memcmp(buf, "\xef\xbb\xbf", 3)) {
+ return (true);
+ }
+ return (false);
+}
diff --git a/lib/isccc/Makefile.am b/lib/isccc/Makefile.am
new file mode 100644
index 0000000..7877bfb
--- /dev/null
+++ b/lib/isccc/Makefile.am
@@ -0,0 +1,38 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libisccc.la
+
+libisccc_ladir = $(includedir)/isccc
+libisccc_la_HEADERS = \
+ include/isccc/alist.h \
+ include/isccc/base64.h \
+ include/isccc/cc.h \
+ include/isccc/ccmsg.h \
+ include/isccc/events.h \
+ include/isccc/sexpr.h \
+ include/isccc/symtab.h \
+ include/isccc/symtype.h \
+ include/isccc/types.h \
+ include/isccc/util.h
+
+libisccc_la_SOURCES = \
+ $(libisccc_la_HEADERS) \
+ alist.c \
+ base64.c \
+ cc.c \
+ ccmsg.c \
+ sexpr.c \
+ symtab.c
+
+libisccc_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISCCC_CFLAGS)
+
+libisccc_la_LIBADD = \
+ $(LIBISC_LIBS)
+
+libisccc_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
diff --git a/lib/isccc/Makefile.in b/lib/isccc/Makefile.in
new file mode 100644
index 0000000..3fee7ca
--- /dev/null
+++ b/lib/isccc/Makefile.in
@@ -0,0 +1,956 @@
+# 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
+
+subdir = lib/isccc
+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 $(libisccc_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)$(libisccc_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libisccc_la_DEPENDENCIES = $(LIBISC_LIBS)
+am__objects_1 =
+am_libisccc_la_OBJECTS = $(am__objects_1) libisccc_la-alist.lo \
+ libisccc_la-base64.lo libisccc_la-cc.lo libisccc_la-ccmsg.lo \
+ libisccc_la-sexpr.lo libisccc_la-symtab.lo
+libisccc_la_OBJECTS = $(am_libisccc_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 =
+libisccc_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libisccc_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)/libisccc_la-alist.Plo \
+ ./$(DEPDIR)/libisccc_la-base64.Plo \
+ ./$(DEPDIR)/libisccc_la-cc.Plo \
+ ./$(DEPDIR)/libisccc_la-ccmsg.Plo \
+ ./$(DEPDIR)/libisccc_la-sexpr.Plo \
+ ./$(DEPDIR)/libisccc_la-symtab.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 = $(libisccc_la_SOURCES)
+DIST_SOURCES = $(libisccc_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libisccc_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 = libisccc.la
+libisccc_ladir = $(includedir)/isccc
+libisccc_la_HEADERS = \
+ include/isccc/alist.h \
+ include/isccc/base64.h \
+ include/isccc/cc.h \
+ include/isccc/ccmsg.h \
+ include/isccc/events.h \
+ include/isccc/sexpr.h \
+ include/isccc/symtab.h \
+ include/isccc/symtype.h \
+ include/isccc/types.h \
+ include/isccc/util.h
+
+libisccc_la_SOURCES = \
+ $(libisccc_la_HEADERS) \
+ alist.c \
+ base64.c \
+ cc.c \
+ ccmsg.c \
+ sexpr.c \
+ symtab.c
+
+libisccc_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISCCC_CFLAGS)
+
+libisccc_la_LIBADD = \
+ $(LIBISC_LIBS)
+
+libisccc_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: 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/isccc/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/isccc/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}; \
+ }
+
+libisccc.la: $(libisccc_la_OBJECTS) $(libisccc_la_DEPENDENCIES) $(EXTRA_libisccc_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libisccc_la_LINK) -rpath $(libdir) $(libisccc_la_OBJECTS) $(libisccc_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-alist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-base64.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-cc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-ccmsg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-sexpr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccc_la-symtab.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 $@ $<
+
+libisccc_la-alist.lo: alist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-alist.lo -MD -MP -MF $(DEPDIR)/libisccc_la-alist.Tpo -c -o libisccc_la-alist.lo `test -f 'alist.c' || echo '$(srcdir)/'`alist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-alist.Tpo $(DEPDIR)/libisccc_la-alist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='alist.c' object='libisccc_la-alist.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-alist.lo `test -f 'alist.c' || echo '$(srcdir)/'`alist.c
+
+libisccc_la-base64.lo: base64.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-base64.lo -MD -MP -MF $(DEPDIR)/libisccc_la-base64.Tpo -c -o libisccc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-base64.Tpo $(DEPDIR)/libisccc_la-base64.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='base64.c' object='libisccc_la-base64.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-base64.lo `test -f 'base64.c' || echo '$(srcdir)/'`base64.c
+
+libisccc_la-cc.lo: cc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-cc.lo -MD -MP -MF $(DEPDIR)/libisccc_la-cc.Tpo -c -o libisccc_la-cc.lo `test -f 'cc.c' || echo '$(srcdir)/'`cc.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-cc.Tpo $(DEPDIR)/libisccc_la-cc.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cc.c' object='libisccc_la-cc.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-cc.lo `test -f 'cc.c' || echo '$(srcdir)/'`cc.c
+
+libisccc_la-ccmsg.lo: ccmsg.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-ccmsg.lo -MD -MP -MF $(DEPDIR)/libisccc_la-ccmsg.Tpo -c -o libisccc_la-ccmsg.lo `test -f 'ccmsg.c' || echo '$(srcdir)/'`ccmsg.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-ccmsg.Tpo $(DEPDIR)/libisccc_la-ccmsg.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ccmsg.c' object='libisccc_la-ccmsg.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-ccmsg.lo `test -f 'ccmsg.c' || echo '$(srcdir)/'`ccmsg.c
+
+libisccc_la-sexpr.lo: sexpr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-sexpr.lo -MD -MP -MF $(DEPDIR)/libisccc_la-sexpr.Tpo -c -o libisccc_la-sexpr.lo `test -f 'sexpr.c' || echo '$(srcdir)/'`sexpr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-sexpr.Tpo $(DEPDIR)/libisccc_la-sexpr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sexpr.c' object='libisccc_la-sexpr.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-sexpr.lo `test -f 'sexpr.c' || echo '$(srcdir)/'`sexpr.c
+
+libisccc_la-symtab.lo: symtab.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccc_la-symtab.lo -MD -MP -MF $(DEPDIR)/libisccc_la-symtab.Tpo -c -o libisccc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccc_la-symtab.Tpo $(DEPDIR)/libisccc_la-symtab.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='symtab.c' object='libisccc_la-symtab.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) $(libisccc_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccc_la-symtab.lo `test -f 'symtab.c' || echo '$(srcdir)/'`symtab.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libisccc_laHEADERS: $(libisccc_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libisccc_la_HEADERS)'; test -n "$(libisccc_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libisccc_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libisccc_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)$(libisccc_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libisccc_ladir)" || exit $$?; \
+ done
+
+uninstall-libisccc_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libisccc_la_HEADERS)'; test -n "$(libisccc_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libisccc_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisccc_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libisccc_la-alist.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-base64.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-cc.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-ccmsg.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-sexpr.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-symtab.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-libisccc_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)/libisccc_la-alist.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-base64.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-cc.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-ccmsg.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-sexpr.Plo
+ -rm -f ./$(DEPDIR)/libisccc_la-symtab.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-libLTLIBRARIES uninstall-libisccc_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libisccc_laHEADERS install-man 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-libLTLIBRARIES uninstall-libisccc_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/isccc/alist.c b/lib/isccc/alist.c
new file mode 100644
index 0000000..182ada0
--- /dev/null
+++ b/lib/isccc/alist.c
@@ -0,0 +1,320 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/assertions.h>
+#include <isc/print.h>
+#include <isc/result.h>
+
+#include <isccc/alist.h>
+#include <isccc/sexpr.h>
+#include <isccc/util.h>
+
+#define CAR(s) (s)->value.as_dottedpair.car
+#define CDR(s) (s)->value.as_dottedpair.cdr
+
+#define ALIST_TAG "*alist*"
+#define MAX_INDENT 64
+
+static char spaces[MAX_INDENT + 1] = " "
+ " ";
+
+isccc_sexpr_t *
+isccc_alist_create(void) {
+ isccc_sexpr_t *alist, *tag;
+
+ tag = isccc_sexpr_fromstring(ALIST_TAG);
+ if (tag == NULL) {
+ return (NULL);
+ }
+ alist = isccc_sexpr_cons(tag, NULL);
+ if (alist == NULL) {
+ isccc_sexpr_free(&tag);
+ return (NULL);
+ }
+
+ return (alist);
+}
+
+bool
+isccc_alist_alistp(isccc_sexpr_t *alist) {
+ isccc_sexpr_t *car;
+
+ if (alist == NULL || alist->type != ISCCC_SEXPRTYPE_DOTTEDPAIR) {
+ return (false);
+ }
+ car = CAR(alist);
+ if (car == NULL || car->type != ISCCC_SEXPRTYPE_STRING) {
+ return (false);
+ }
+ if (strcmp(car->value.as_string, ALIST_TAG) != 0) {
+ return (false);
+ }
+ return (true);
+}
+
+bool
+isccc_alist_emptyp(isccc_sexpr_t *alist) {
+ REQUIRE(isccc_alist_alistp(alist));
+
+ if (CDR(alist) == NULL) {
+ return (true);
+ }
+ return (false);
+}
+
+isccc_sexpr_t *
+isccc_alist_first(isccc_sexpr_t *alist) {
+ REQUIRE(isccc_alist_alistp(alist));
+
+ return (CDR(alist));
+}
+
+isccc_sexpr_t *
+isccc_alist_assq(isccc_sexpr_t *alist, const char *key) {
+ isccc_sexpr_t *car, *caar;
+
+ REQUIRE(isccc_alist_alistp(alist));
+
+ /*
+ * Skip alist type tag.
+ */
+ alist = CDR(alist);
+
+ while (alist != NULL) {
+ INSIST(alist->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+ car = CAR(alist);
+ INSIST(car->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+ caar = CAR(car);
+ if (caar->type == ISCCC_SEXPRTYPE_STRING &&
+ strcmp(caar->value.as_string, key) == 0)
+ {
+ return (car);
+ }
+ alist = CDR(alist);
+ }
+
+ return (NULL);
+}
+
+void
+isccc_alist_delete(isccc_sexpr_t *alist, const char *key) {
+ isccc_sexpr_t *car, *caar, *rest, *prev;
+
+ REQUIRE(isccc_alist_alistp(alist));
+
+ prev = alist;
+ rest = CDR(alist);
+ while (rest != NULL) {
+ INSIST(rest->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+ car = CAR(rest);
+ INSIST(car != NULL && car->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+ caar = CAR(car);
+ if (caar->type == ISCCC_SEXPRTYPE_STRING &&
+ strcmp(caar->value.as_string, key) == 0)
+ {
+ CDR(prev) = CDR(rest);
+ CDR(rest) = NULL;
+ isccc_sexpr_free(&rest);
+ break;
+ }
+ prev = rest;
+ rest = CDR(rest);
+ }
+}
+
+isccc_sexpr_t *
+isccc_alist_define(isccc_sexpr_t *alist, const char *key,
+ isccc_sexpr_t *value) {
+ isccc_sexpr_t *kv, *k, *elt;
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv == NULL) {
+ /*
+ * New association.
+ */
+ k = isccc_sexpr_fromstring(key);
+ if (k == NULL) {
+ return (NULL);
+ }
+ kv = isccc_sexpr_cons(k, value);
+ if (kv == NULL) {
+ isccc_sexpr_free(&kv);
+ return (NULL);
+ }
+ elt = isccc_sexpr_addtolist(&alist, kv);
+ if (elt == NULL) {
+ isccc_sexpr_free(&kv);
+ return (NULL);
+ }
+ } else {
+ /*
+ * We've already got an entry for this key. Replace it.
+ */
+ isccc_sexpr_free(&CDR(kv));
+ CDR(kv) = value;
+ }
+
+ return (kv);
+}
+
+isccc_sexpr_t *
+isccc_alist_definestring(isccc_sexpr_t *alist, const char *key,
+ const char *str) {
+ isccc_sexpr_t *v, *kv;
+
+ v = isccc_sexpr_fromstring(str);
+ if (v == NULL) {
+ return (NULL);
+ }
+ kv = isccc_alist_define(alist, key, v);
+ if (kv == NULL) {
+ isccc_sexpr_free(&v);
+ }
+
+ return (kv);
+}
+
+isccc_sexpr_t *
+isccc_alist_definebinary(isccc_sexpr_t *alist, const char *key,
+ isccc_region_t *r) {
+ isccc_sexpr_t *v, *kv;
+
+ v = isccc_sexpr_frombinary(r);
+ if (v == NULL) {
+ return (NULL);
+ }
+ kv = isccc_alist_define(alist, key, v);
+ if (kv == NULL) {
+ isccc_sexpr_free(&v);
+ }
+
+ return (kv);
+}
+
+isccc_sexpr_t *
+isccc_alist_lookup(isccc_sexpr_t *alist, const char *key) {
+ isccc_sexpr_t *kv;
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv != NULL) {
+ return (CDR(kv));
+ }
+ return (NULL);
+}
+
+isc_result_t
+isccc_alist_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp) {
+ isccc_sexpr_t *kv, *v;
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv != NULL) {
+ v = CDR(kv);
+ if (isccc_sexpr_stringp(v)) {
+ if (strp != NULL) {
+ *strp = isccc_sexpr_tostring(v);
+ }
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_EXISTS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+isccc_alist_lookupbinary(isccc_sexpr_t *alist, const char *key,
+ isccc_region_t **r) {
+ isccc_sexpr_t *kv, *v;
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv != NULL) {
+ v = CDR(kv);
+ if (isccc_sexpr_binaryp(v)) {
+ if (r != NULL) {
+ *r = isccc_sexpr_tobinary(v);
+ }
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_EXISTS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+void
+isccc_alist_prettyprint(isccc_sexpr_t *sexpr, unsigned int indent,
+ FILE *stream) {
+ isccc_sexpr_t *elt, *kv, *k, *v;
+
+ if (isccc_alist_alistp(sexpr)) {
+ fprintf(stream, "{\n");
+ indent += 4;
+ for (elt = isccc_alist_first(sexpr); elt != NULL;
+ elt = CDR(elt))
+ {
+ kv = CAR(elt);
+ INSIST(isccc_sexpr_listp(kv));
+ k = CAR(kv);
+ v = CDR(kv);
+ INSIST(isccc_sexpr_stringp(k));
+ fprintf(stream, "%.*s%s => ", (int)indent, spaces,
+ isccc_sexpr_tostring(k));
+ isccc_alist_prettyprint(v, indent, stream);
+ if (CDR(elt) != NULL) {
+ fprintf(stream, ",");
+ }
+ fprintf(stream, "\n");
+ }
+ indent -= 4;
+ fprintf(stream, "%.*s}", (int)indent, spaces);
+ } else if (isccc_sexpr_listp(sexpr)) {
+ fprintf(stream, "(\n");
+ indent += 4;
+ for (elt = sexpr; elt != NULL; elt = CDR(elt)) {
+ fprintf(stream, "%.*s", (int)indent, spaces);
+ isccc_alist_prettyprint(CAR(elt), indent, stream);
+ if (CDR(elt) != NULL) {
+ fprintf(stream, ",");
+ }
+ fprintf(stream, "\n");
+ }
+ indent -= 4;
+ fprintf(stream, "%.*s)", (int)indent, spaces);
+ } else {
+ isccc_sexpr_print(sexpr, stream);
+ }
+}
diff --git a/lib/isccc/base64.c b/lib/isccc/base64.c
new file mode 100644
index 0000000..fd0f381
--- /dev/null
+++ b/lib/isccc/base64.c
@@ -0,0 +1,74 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <isc/base64.h>
+#include <isc/buffer.h>
+#include <isc/region.h>
+#include <isc/result.h>
+
+#include <isccc/base64.h>
+#include <isccc/util.h>
+
+isc_result_t
+isccc_base64_encode(isccc_region_t *source, int wordlength,
+ const char *wordbreak, isccc_region_t *target) {
+ isc_region_t sr;
+ isc_buffer_t tb;
+ isc_result_t result;
+
+ sr.base = source->rstart;
+ sr.length = (unsigned int)(source->rend - source->rstart);
+ isc_buffer_init(&tb, target->rstart,
+ (unsigned int)(target->rend - target->rstart));
+
+ result = isc_base64_totext(&sr, wordlength, wordbreak, &tb);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ source->rstart = source->rend;
+ target->rstart = isc_buffer_used(&tb);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccc_base64_decode(const char *cstr, isccc_region_t *target) {
+ isc_buffer_t b;
+ isc_result_t result;
+
+ isc_buffer_init(&b, target->rstart,
+ (unsigned int)(target->rend - target->rstart));
+ result = isc_base64_decodestring(cstr, &b);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ target->rstart = isc_buffer_used(&b);
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isccc/cc.c b/lib/isccc/cc.c
new file mode 100644
index 0000000..cbd9bad
--- /dev/null
+++ b/lib/isccc/cc.c
@@ -0,0 +1,1057 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/assertions.h>
+#include <isc/hmac.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/safe.h>
+
+#include <isccc/alist.h>
+#include <isccc/base64.h>
+#include <isccc/cc.h>
+#include <isccc/sexpr.h>
+#include <isccc/symtab.h>
+#include <isccc/symtype.h>
+#include <isccc/util.h>
+
+#define MAX_TAGS 256
+#define DUP_LIFETIME 900
+#ifndef ISCCC_MAXDEPTH
+#define ISCCC_MAXDEPTH \
+ 10 /* Big enough for rndc which just sends a string each way. */
+#endif
+
+typedef isccc_sexpr_t *sexpr_ptr;
+
+static unsigned char auth_hmd5[] = {
+ 0x05, 0x5f, 0x61, 0x75, 0x74, 0x68, /*%< len + _auth */
+ ISCCC_CCMSGTYPE_TABLE, /*%< message type */
+ 0x00, 0x00, 0x00, 0x20, /*%< length == 32 */
+ 0x04, 0x68, 0x6d, 0x64, 0x35, /*%< len + hmd5 */
+ ISCCC_CCMSGTYPE_BINARYDATA, /*%< message type */
+ 0x00, 0x00, 0x00, 0x16, /*%< length == 22 */
+ /*
+ * The base64 encoding of one of our HMAC-MD5 signatures is
+ * 22 bytes.
+ */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+#define HMD5_OFFSET 21 /*%< 21 = 6 + 1 + 4 + 5 + 1 + 4 */
+#define HMD5_LENGTH 22
+
+static unsigned char auth_hsha[] = {
+ 0x05, 0x5f, 0x61, 0x75, 0x74, 0x68, /*%< len + _auth */
+ ISCCC_CCMSGTYPE_TABLE, /*%< message type */
+ 0x00, 0x00, 0x00, 0x63, /*%< length == 99 */
+ 0x04, 0x68, 0x73, 0x68, 0x61, /*%< len + hsha */
+ ISCCC_CCMSGTYPE_BINARYDATA, /*%< message type */
+ 0x00, 0x00, 0x00, 0x59, /*%< length == 89 */
+ 0x00, /*%< algorithm */
+ /*
+ * The base64 encoding of one of our HMAC-SHA* signatures is
+ * 88 bytes.
+ */
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+#define HSHA_OFFSET 22 /*%< 21 = 6 + 1 + 4 + 5 + 1 + 4 + 1 */
+#define HSHA_LENGTH 88
+
+static isc_result_t
+table_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer);
+
+static isc_result_t
+list_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer);
+
+static isc_result_t
+value_towire(isccc_sexpr_t *elt, isc_buffer_t **buffer) {
+ unsigned int len;
+ isccc_region_t *vr;
+ isc_result_t result;
+
+ if (isccc_sexpr_binaryp(elt)) {
+ vr = isccc_sexpr_tobinary(elt);
+ len = REGION_SIZE(*vr);
+ result = isc_buffer_reserve(buffer, 1 + 4);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_BINARYDATA);
+ isc_buffer_putuint32(*buffer, len);
+
+ result = isc_buffer_reserve(buffer, len);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putmem(*buffer, vr->rstart, len);
+ } else if (isccc_alist_alistp(elt)) {
+ unsigned int used;
+ isc_buffer_t b;
+
+ result = isc_buffer_reserve(buffer, 1 + 4);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_TABLE);
+ /*
+ * Emit a placeholder length.
+ */
+ used = (*buffer)->used;
+ isc_buffer_putuint32(*buffer, 0);
+
+ /*
+ * Emit the table.
+ */
+ result = table_towire(elt, buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ len = (*buffer)->used - used;
+ /*
+ * 'len' is 4 bytes too big, since it counts
+ * the placeholder length too. Adjust and
+ * emit.
+ */
+ INSIST(len >= 4U);
+ len -= 4;
+
+ isc_buffer_init(&b, (unsigned char *)(*buffer)->base + used, 4);
+ isc_buffer_putuint32(&b, len);
+ } else if (isccc_sexpr_listp(elt)) {
+ unsigned int used;
+ isc_buffer_t b;
+
+ result = isc_buffer_reserve(buffer, 1 + 4);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(*buffer, ISCCC_CCMSGTYPE_LIST);
+ /*
+ * Emit a placeholder length.
+ */
+ used = (*buffer)->used;
+ isc_buffer_putuint32(*buffer, 0);
+
+ /*
+ * Emit the list.
+ */
+ result = list_towire(elt, buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ len = (*buffer)->used - used;
+ /*
+ * 'len' is 4 bytes too big, since it counts
+ * the placeholder length too. Adjust and
+ * emit.
+ */
+ INSIST(len >= 4U);
+ len -= 4;
+
+ isc_buffer_init(&b, (unsigned char *)(*buffer)->base + used, 4);
+ isc_buffer_putuint32(&b, len);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+table_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer) {
+ isccc_sexpr_t *kv, *elt, *k, *v;
+ char *ks;
+ isc_result_t result;
+ unsigned int len;
+
+ for (elt = isccc_alist_first(alist); elt != NULL;
+ elt = ISCCC_SEXPR_CDR(elt))
+ {
+ kv = ISCCC_SEXPR_CAR(elt);
+ k = ISCCC_SEXPR_CAR(kv);
+ ks = isccc_sexpr_tostring(k);
+ v = ISCCC_SEXPR_CDR(kv);
+ len = (unsigned int)strlen(ks);
+ INSIST(len <= 255U);
+ /*
+ * Emit the key name.
+ */
+ result = isc_buffer_reserve(buffer, 1 + len);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+ isc_buffer_putuint8(*buffer, (uint8_t)len);
+ isc_buffer_putmem(*buffer, (const unsigned char *)ks, len);
+ /*
+ * Emit the value.
+ */
+ result = value_towire(v, buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+list_towire(isccc_sexpr_t *list, isc_buffer_t **buffer) {
+ isc_result_t result;
+
+ while (list != NULL) {
+ result = value_towire(ISCCC_SEXPR_CAR(list), buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ list = ISCCC_SEXPR_CDR(list);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+sign(unsigned char *data, unsigned int length, unsigned char *hmac,
+ uint32_t algorithm, isccc_region_t *secret) {
+ const isc_md_type_t *md_type;
+ isc_result_t result;
+ isccc_region_t source, target;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen = sizeof(digest);
+ unsigned char digestb64[HSHA_LENGTH + 4];
+
+ source.rstart = digest;
+
+ switch (algorithm) {
+ case ISCCC_ALG_HMACMD5:
+ md_type = ISC_MD_MD5;
+ break;
+ case ISCCC_ALG_HMACSHA1:
+ md_type = ISC_MD_SHA1;
+ break;
+ case ISCCC_ALG_HMACSHA224:
+ md_type = ISC_MD_SHA224;
+ break;
+ case ISCCC_ALG_HMACSHA256:
+ md_type = ISC_MD_SHA256;
+ break;
+ case ISCCC_ALG_HMACSHA384:
+ md_type = ISC_MD_SHA384;
+ break;
+ case ISCCC_ALG_HMACSHA512:
+ md_type = ISC_MD_SHA512;
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = isc_hmac(md_type, secret->rstart, REGION_SIZE(*secret), data,
+ length, digest, &digestlen);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ source.rend = digest + digestlen;
+
+ memset(digestb64, 0, sizeof(digestb64));
+ target.rstart = digestb64;
+ target.rend = digestb64 + sizeof(digestb64);
+ result = isccc_base64_encode(&source, 64, "", &target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (algorithm == ISCCC_ALG_HMACMD5) {
+ PUT_MEM(digestb64, HMD5_LENGTH, hmac);
+ } else {
+ PUT_MEM(digestb64, HSHA_LENGTH, hmac);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccc_cc_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer, uint32_t algorithm,
+ isccc_region_t *secret) {
+ unsigned int hmac_base, signed_base;
+ isc_result_t result;
+
+ result = isc_buffer_reserve(buffer,
+ 4 + ((algorithm == ISCCC_ALG_HMACMD5)
+ ? sizeof(auth_hmd5)
+ : sizeof(auth_hsha)));
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOSPACE);
+ }
+
+ /*
+ * Emit protocol version.
+ */
+ isc_buffer_putuint32(*buffer, 1);
+
+ if (secret != NULL) {
+ /*
+ * Emit _auth section with zeroed HMAC signature.
+ * We'll replace the zeros with the real signature once
+ * we know what it is.
+ */
+ if (algorithm == ISCCC_ALG_HMACMD5) {
+ hmac_base = (*buffer)->used + HMD5_OFFSET;
+ isc_buffer_putmem(*buffer, auth_hmd5,
+ sizeof(auth_hmd5));
+ } else {
+ unsigned char *hmac_alg;
+
+ hmac_base = (*buffer)->used + HSHA_OFFSET;
+ hmac_alg = (unsigned char *)isc_buffer_used(*buffer) +
+ HSHA_OFFSET - 1;
+ isc_buffer_putmem(*buffer, auth_hsha,
+ sizeof(auth_hsha));
+ *hmac_alg = algorithm;
+ }
+ } else {
+ hmac_base = 0;
+ }
+ signed_base = (*buffer)->used;
+ /*
+ * Delete any existing _auth section so that we don't try
+ * to encode it.
+ */
+ isccc_alist_delete(alist, "_auth");
+ /*
+ * Emit the message.
+ */
+ result = table_towire(alist, buffer);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ if (secret != NULL) {
+ return (sign((unsigned char *)(*buffer)->base + signed_base,
+ (*buffer)->used - signed_base,
+ (unsigned char *)(*buffer)->base + hmac_base,
+ algorithm, secret));
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length,
+ uint32_t algorithm, isccc_region_t *secret) {
+ const isc_md_type_t *md_type;
+ isccc_region_t source;
+ isccc_region_t target;
+ isc_result_t result;
+ isccc_sexpr_t *_auth, *hmac;
+ unsigned char digest[ISC_MAX_MD_SIZE];
+ unsigned int digestlen = sizeof(digest);
+ unsigned char digestb64[HSHA_LENGTH * 4];
+
+ /*
+ * Extract digest.
+ */
+ _auth = isccc_alist_lookup(alist, "_auth");
+ if (!isccc_alist_alistp(_auth)) {
+ return (ISC_R_FAILURE);
+ }
+ if (algorithm == ISCCC_ALG_HMACMD5) {
+ hmac = isccc_alist_lookup(_auth, "hmd5");
+ } else {
+ hmac = isccc_alist_lookup(_auth, "hsha");
+ }
+ if (!isccc_sexpr_binaryp(hmac)) {
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * Compute digest.
+ */
+ source.rstart = digest;
+
+ switch (algorithm) {
+ case ISCCC_ALG_HMACMD5:
+ md_type = ISC_MD_MD5;
+ break;
+ case ISCCC_ALG_HMACSHA1:
+ md_type = ISC_MD_SHA1;
+ break;
+ case ISCCC_ALG_HMACSHA224:
+ md_type = ISC_MD_SHA224;
+ break;
+ case ISCCC_ALG_HMACSHA256:
+ md_type = ISC_MD_SHA256;
+ break;
+ case ISCCC_ALG_HMACSHA384:
+ md_type = ISC_MD_SHA384;
+ break;
+ case ISCCC_ALG_HMACSHA512:
+ md_type = ISC_MD_SHA512;
+ break;
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+
+ result = isc_hmac(md_type, secret->rstart, REGION_SIZE(*secret), data,
+ length, digest, &digestlen);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ source.rend = digest + digestlen;
+
+ target.rstart = digestb64;
+ target.rend = digestb64 + sizeof(digestb64);
+ memset(digestb64, 0, sizeof(digestb64));
+ result = isccc_base64_encode(&source, 64, "", &target);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Verify.
+ */
+ if (algorithm == ISCCC_ALG_HMACMD5) {
+ isccc_region_t *region;
+ unsigned char *value;
+
+ region = isccc_sexpr_tobinary(hmac);
+ if ((region->rend - region->rstart) != HMD5_LENGTH) {
+ return (ISCCC_R_BADAUTH);
+ }
+ value = region->rstart;
+ if (!isc_safe_memequal(value, digestb64, HMD5_LENGTH)) {
+ return (ISCCC_R_BADAUTH);
+ }
+ } else {
+ isccc_region_t *region;
+ unsigned char *value;
+ uint32_t valalg;
+
+ region = isccc_sexpr_tobinary(hmac);
+
+ /*
+ * Note: with non-MD5 algorithms, there's an extra octet
+ * to identify which algorithm is in use.
+ */
+ if ((region->rend - region->rstart) != HSHA_LENGTH + 1) {
+ return (ISCCC_R_BADAUTH);
+ }
+ value = region->rstart;
+ GET8(valalg, value);
+ if ((valalg != algorithm) ||
+ !isc_safe_memequal(value, digestb64, HSHA_LENGTH))
+ {
+ return (ISCCC_R_BADAUTH);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+table_fromwire(isccc_region_t *source, isccc_region_t *secret,
+ uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp);
+
+static isc_result_t
+list_fromwire(isccc_region_t *source, unsigned int depth,
+ isccc_sexpr_t **listp);
+
+static isc_result_t
+value_fromwire(isccc_region_t *source, unsigned int depth,
+ isccc_sexpr_t **valuep) {
+ unsigned int msgtype;
+ uint32_t len;
+ isccc_sexpr_t *value;
+ isccc_region_t active;
+ isc_result_t result;
+
+ if (depth > ISCCC_MAXDEPTH) {
+ return (ISCCC_R_MAXDEPTH);
+ }
+
+ if (REGION_SIZE(*source) < 1 + 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ GET8(msgtype, source->rstart);
+ GET32(len, source->rstart);
+ if (REGION_SIZE(*source) < len) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ active.rstart = source->rstart;
+ active.rend = active.rstart + len;
+ source->rstart = active.rend;
+ if (msgtype == ISCCC_CCMSGTYPE_BINARYDATA) {
+ value = isccc_sexpr_frombinary(&active);
+ if (value != NULL) {
+ *valuep = value;
+ result = ISC_R_SUCCESS;
+ } else {
+ result = ISC_R_NOMEMORY;
+ }
+ } else if (msgtype == ISCCC_CCMSGTYPE_TABLE) {
+ result = table_fromwire(&active, NULL, 0, depth + 1, valuep);
+ } else if (msgtype == ISCCC_CCMSGTYPE_LIST) {
+ result = list_fromwire(&active, depth + 1, valuep);
+ } else {
+ result = ISCCC_R_SYNTAX;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+table_fromwire(isccc_region_t *source, isccc_region_t *secret,
+ uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp) {
+ char key[256];
+ uint32_t len;
+ isc_result_t result;
+ isccc_sexpr_t *alist, *value;
+ bool first_tag;
+ unsigned char *checksum_rstart;
+
+ REQUIRE(alistp != NULL && *alistp == NULL);
+
+ if (depth > ISCCC_MAXDEPTH) {
+ return (ISCCC_R_MAXDEPTH);
+ }
+
+ checksum_rstart = NULL;
+ first_tag = true;
+ alist = isccc_alist_create();
+ if (alist == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ while (!REGION_EMPTY(*source)) {
+ GET8(len, source->rstart);
+ if (REGION_SIZE(*source) < len) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto bad;
+ }
+ GET_MEM(key, len, source->rstart);
+ key[len] = '\0'; /* Ensure NUL termination. */
+ value = NULL;
+ result = value_fromwire(source, depth + 1, &value);
+ if (result != ISC_R_SUCCESS) {
+ goto bad;
+ }
+ if (isccc_alist_define(alist, key, value) == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto bad;
+ }
+ if (first_tag && secret != NULL && strcmp(key, "_auth") == 0) {
+ checksum_rstart = source->rstart;
+ }
+ first_tag = false;
+ }
+
+ if (secret != NULL) {
+ if (checksum_rstart != NULL) {
+ result = verify(
+ alist, checksum_rstart,
+ (unsigned int)(source->rend - checksum_rstart),
+ algorithm, secret);
+ } else {
+ result = ISCCC_R_BADAUTH;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+bad:
+ if (result == ISC_R_SUCCESS) {
+ *alistp = alist;
+ } else {
+ isccc_sexpr_free(&alist);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+list_fromwire(isccc_region_t *source, unsigned int depth,
+ isccc_sexpr_t **listp) {
+ isccc_sexpr_t *list, *value;
+ isc_result_t result;
+
+ if (depth > ISCCC_MAXDEPTH) {
+ return (ISCCC_R_MAXDEPTH);
+ }
+
+ list = NULL;
+ while (!REGION_EMPTY(*source)) {
+ value = NULL;
+ result = value_fromwire(source, depth + 1, &value);
+ if (result != ISC_R_SUCCESS) {
+ isccc_sexpr_free(&list);
+ return (result);
+ }
+ if (isccc_sexpr_addtolist(&list, value) == NULL) {
+ isccc_sexpr_free(&value);
+ isccc_sexpr_free(&list);
+ return (ISC_R_NOMEMORY);
+ }
+ }
+
+ *listp = list;
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccc_cc_fromwire(isccc_region_t *source, isccc_sexpr_t **alistp,
+ uint32_t algorithm, isccc_region_t *secret) {
+ unsigned int size;
+ uint32_t version;
+
+ size = REGION_SIZE(*source);
+ if (size < 4) {
+ return (ISC_R_UNEXPECTEDEND);
+ }
+ GET32(version, source->rstart);
+ if (version != 1) {
+ return (ISCCC_R_UNKNOWNVERSION);
+ }
+
+ return (table_fromwire(source, secret, algorithm, 0, alistp));
+}
+
+static isc_result_t
+createmessage(uint32_t version, const char *from, const char *to,
+ uint32_t serial, isccc_time_t now, isccc_time_t expires,
+ isccc_sexpr_t **alistp, bool want_expires) {
+ isccc_sexpr_t *alist, *_ctrl, *_data;
+ isc_result_t result;
+
+ REQUIRE(alistp != NULL && *alistp == NULL);
+
+ if (version != 1) {
+ return (ISCCC_R_UNKNOWNVERSION);
+ }
+
+ alist = isccc_alist_create();
+ if (alist == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ result = ISC_R_NOMEMORY;
+
+ _ctrl = isccc_alist_create();
+ if (_ctrl == NULL) {
+ goto bad;
+ }
+ if (isccc_alist_define(alist, "_ctrl", _ctrl) == NULL) {
+ isccc_sexpr_free(&_ctrl);
+ goto bad;
+ }
+
+ _data = isccc_alist_create();
+ if (_data == NULL) {
+ goto bad;
+ }
+ if (isccc_alist_define(alist, "_data", _data) == NULL) {
+ isccc_sexpr_free(&_data);
+ goto bad;
+ }
+
+ if (isccc_cc_defineuint32(_ctrl, "_ser", serial) == NULL ||
+ isccc_cc_defineuint32(_ctrl, "_tim", now) == NULL ||
+ (want_expires &&
+ isccc_cc_defineuint32(_ctrl, "_exp", expires) == NULL))
+ {
+ goto bad;
+ }
+ if (from != NULL && isccc_cc_definestring(_ctrl, "_frm", from) == NULL)
+ {
+ goto bad;
+ }
+ if (to != NULL && isccc_cc_definestring(_ctrl, "_to", to) == NULL) {
+ goto bad;
+ }
+
+ *alistp = alist;
+
+ return (ISC_R_SUCCESS);
+
+bad:
+ isccc_sexpr_free(&alist);
+
+ return (result);
+}
+
+isc_result_t
+isccc_cc_createmessage(uint32_t version, const char *from, const char *to,
+ uint32_t serial, isccc_time_t now, isccc_time_t expires,
+ isccc_sexpr_t **alistp) {
+ return (createmessage(version, from, to, serial, now, expires, alistp,
+ true));
+}
+
+isc_result_t
+isccc_cc_createack(isccc_sexpr_t *message, bool ok, isccc_sexpr_t **ackp) {
+ char *_frm, *_to;
+ uint32_t serial;
+ isccc_sexpr_t *ack, *_ctrl;
+ isc_result_t result;
+ isccc_time_t t;
+
+ REQUIRE(ackp != NULL && *ackp == NULL);
+
+ _ctrl = isccc_alist_lookup(message, "_ctrl");
+ if (!isccc_alist_alistp(_ctrl) ||
+ isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
+ isccc_cc_lookupuint32(_ctrl, "_tim", &t) != ISC_R_SUCCESS)
+ {
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * _frm and _to are optional.
+ */
+ _frm = NULL;
+ (void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
+ _to = NULL;
+ (void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
+ /*
+ * Create the ack.
+ */
+ ack = NULL;
+ result = createmessage(1, _to, _frm, serial, t, 0, &ack, false);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ _ctrl = isccc_alist_lookup(ack, "_ctrl");
+ if (_ctrl == NULL) {
+ result = ISC_R_FAILURE;
+ goto bad;
+ }
+ if (isccc_cc_definestring(ack, "_ack", (ok) ? "1" : "0") == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto bad;
+ }
+
+ *ackp = ack;
+
+ return (ISC_R_SUCCESS);
+
+bad:
+ isccc_sexpr_free(&ack);
+
+ return (result);
+}
+
+bool
+isccc_cc_isack(isccc_sexpr_t *message) {
+ isccc_sexpr_t *_ctrl;
+
+ _ctrl = isccc_alist_lookup(message, "_ctrl");
+ if (!isccc_alist_alistp(_ctrl)) {
+ return (false);
+ }
+ if (isccc_cc_lookupstring(_ctrl, "_ack", NULL) == ISC_R_SUCCESS) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+isccc_cc_isreply(isccc_sexpr_t *message) {
+ isccc_sexpr_t *_ctrl;
+
+ _ctrl = isccc_alist_lookup(message, "_ctrl");
+ if (!isccc_alist_alistp(_ctrl)) {
+ return (false);
+ }
+ if (isccc_cc_lookupstring(_ctrl, "_rpl", NULL) == ISC_R_SUCCESS) {
+ return (true);
+ }
+ return (false);
+}
+
+isc_result_t
+isccc_cc_createresponse(isccc_sexpr_t *message, isccc_time_t now,
+ isccc_time_t expires, isccc_sexpr_t **alistp) {
+ char *_frm, *_to, *type = NULL;
+ uint32_t serial;
+ isccc_sexpr_t *alist, *_ctrl, *_data;
+ isc_result_t result;
+
+ REQUIRE(alistp != NULL && *alistp == NULL);
+
+ _ctrl = isccc_alist_lookup(message, "_ctrl");
+ _data = isccc_alist_lookup(message, "_data");
+ if (!isccc_alist_alistp(_ctrl) || !isccc_alist_alistp(_data) ||
+ isccc_cc_lookupuint32(_ctrl, "_ser", &serial) != ISC_R_SUCCESS ||
+ isccc_cc_lookupstring(_data, "type", &type) != ISC_R_SUCCESS)
+ {
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * _frm and _to are optional.
+ */
+ _frm = NULL;
+ (void)isccc_cc_lookupstring(_ctrl, "_frm", &_frm);
+ _to = NULL;
+ (void)isccc_cc_lookupstring(_ctrl, "_to", &_to);
+ /*
+ * Create the response.
+ */
+ alist = NULL;
+ result = isccc_cc_createmessage(1, _to, _frm, serial, now, expires,
+ &alist);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ _ctrl = isccc_alist_lookup(alist, "_ctrl");
+ if (_ctrl == NULL) {
+ result = ISC_R_FAILURE;
+ goto bad;
+ }
+
+ _data = isccc_alist_lookup(alist, "_data");
+ if (_data == NULL) {
+ result = ISC_R_FAILURE;
+ goto bad;
+ }
+
+ if (isccc_cc_definestring(_ctrl, "_rpl", "1") == NULL ||
+ isccc_cc_definestring(_data, "type", type) == NULL)
+ {
+ result = ISC_R_NOMEMORY;
+ goto bad;
+ }
+
+ *alistp = alist;
+
+ return (ISC_R_SUCCESS);
+
+bad:
+ isccc_sexpr_free(&alist);
+ return (result);
+}
+
+isccc_sexpr_t *
+isccc_cc_definestring(isccc_sexpr_t *alist, const char *key, const char *str) {
+ size_t len;
+ isccc_region_t r;
+
+ len = strlen(str);
+ DE_CONST(str, r.rstart);
+ r.rend = r.rstart + len;
+
+ return (isccc_alist_definebinary(alist, key, &r));
+}
+
+isccc_sexpr_t *
+isccc_cc_defineuint32(isccc_sexpr_t *alist, const char *key, uint32_t i) {
+ char b[100];
+ size_t len;
+ isccc_region_t r;
+
+ snprintf(b, sizeof(b), "%u", i);
+ len = strlen(b);
+ r.rstart = (unsigned char *)b;
+ r.rend = (unsigned char *)b + len;
+
+ return (isccc_alist_definebinary(alist, key, &r));
+}
+
+isc_result_t
+isccc_cc_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp) {
+ isccc_sexpr_t *kv, *v;
+
+ REQUIRE(strp == NULL || *strp == NULL);
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv != NULL) {
+ v = ISCCC_SEXPR_CDR(kv);
+ if (isccc_sexpr_binaryp(v)) {
+ if (strp != NULL) {
+ *strp = isccc_sexpr_tostring(v);
+ }
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_EXISTS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+isc_result_t
+isccc_cc_lookupuint32(isccc_sexpr_t *alist, const char *key, uint32_t *uintp) {
+ isccc_sexpr_t *kv, *v;
+
+ kv = isccc_alist_assq(alist, key);
+ if (kv != NULL) {
+ v = ISCCC_SEXPR_CDR(kv);
+ if (isccc_sexpr_binaryp(v)) {
+ if (uintp != NULL) {
+ *uintp = (uint32_t)strtoul(
+ isccc_sexpr_tostring(v), NULL, 10);
+ }
+ return (ISC_R_SUCCESS);
+ } else {
+ return (ISC_R_EXISTS);
+ }
+ }
+
+ return (ISC_R_NOTFOUND);
+}
+
+static void
+symtab_undefine(char *key, unsigned int type, isccc_symvalue_t value,
+ void *arg) {
+ UNUSED(type);
+ UNUSED(value);
+ UNUSED(arg);
+
+ free(key);
+}
+
+static bool
+symtab_clean(char *key, unsigned int type, isccc_symvalue_t value, void *arg) {
+ isccc_time_t *now;
+
+ UNUSED(key);
+ UNUSED(type);
+
+ now = arg;
+
+ if (*now < value.as_uinteger) {
+ return (false);
+ }
+ if ((*now - value.as_uinteger) < DUP_LIFETIME) {
+ return (false);
+ }
+ return (true);
+}
+
+isc_result_t
+isccc_cc_createsymtab(isccc_symtab_t **symtabp) {
+ return (isccc_symtab_create(11897, symtab_undefine, NULL, false,
+ symtabp));
+}
+
+void
+isccc_cc_cleansymtab(isccc_symtab_t *symtab, isccc_time_t now) {
+ isccc_symtab_foreach(symtab, symtab_clean, &now);
+}
+
+static bool
+has_whitespace(const char *str) {
+ char c;
+
+ if (str == NULL) {
+ return (false);
+ }
+ while ((c = *str++) != '\0') {
+ if (c == ' ' || c == '\t' || c == '\n') {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+isc_result_t
+isccc_cc_checkdup(isccc_symtab_t *symtab, isccc_sexpr_t *message,
+ isccc_time_t now) {
+ const char *_frm;
+ const char *_to;
+ char *_ser = NULL, *_tim = NULL, *tmp;
+ isc_result_t result;
+ char *key;
+ size_t len;
+ isccc_symvalue_t value;
+ isccc_sexpr_t *_ctrl;
+
+ _ctrl = isccc_alist_lookup(message, "_ctrl");
+ if (!isccc_alist_alistp(_ctrl) ||
+ isccc_cc_lookupstring(_ctrl, "_ser", &_ser) != ISC_R_SUCCESS ||
+ isccc_cc_lookupstring(_ctrl, "_tim", &_tim) != ISC_R_SUCCESS)
+ {
+ return (ISC_R_FAILURE);
+ }
+
+ INSIST(_ser != NULL);
+ INSIST(_tim != NULL);
+
+ /*
+ * _frm and _to are optional.
+ */
+ tmp = NULL;
+ if (isccc_cc_lookupstring(_ctrl, "_frm", &tmp) != ISC_R_SUCCESS) {
+ _frm = "";
+ } else {
+ _frm = tmp;
+ }
+ tmp = NULL;
+ if (isccc_cc_lookupstring(_ctrl, "_to", &tmp) != ISC_R_SUCCESS) {
+ _to = "";
+ } else {
+ _to = tmp;
+ }
+ /*
+ * Ensure there is no newline in any of the strings. This is so
+ * we can write them to a file later.
+ */
+ if (has_whitespace(_frm) || has_whitespace(_to) ||
+ has_whitespace(_ser) || has_whitespace(_tim))
+ {
+ return (ISC_R_FAILURE);
+ }
+ len = strlen(_frm) + strlen(_to) + strlen(_ser) + strlen(_tim) + 4;
+ key = malloc(len);
+ if (key == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ snprintf(key, len, "%s;%s;%s;%s", _frm, _to, _ser, _tim);
+ value.as_uinteger = now;
+ result = isccc_symtab_define(symtab, key, ISCCC_SYMTYPE_CCDUP, value,
+ isccc_symexists_reject);
+ if (result != ISC_R_SUCCESS) {
+ free(key);
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
diff --git a/lib/isccc/ccmsg.c b/lib/isccc/ccmsg.c
new file mode 100644
index 0000000..9ee48ab
--- /dev/null
+++ b/lib/isccc/ccmsg.c
@@ -0,0 +1,185 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <inttypes.h>
+
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <isccc/ccmsg.h>
+#include <isccc/events.h>
+
+#define CCMSG_MAGIC ISC_MAGIC('C', 'C', 'm', 's')
+#define VALID_CCMSG(foo) ISC_MAGIC_VALID(foo, CCMSG_MAGIC)
+
+static void
+recv_data(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *arg) {
+ isccc_ccmsg_t *ccmsg = arg;
+ size_t size;
+
+ INSIST(VALID_CCMSG(ccmsg));
+
+ switch (eresult) {
+ case ISC_R_SHUTTINGDOWN:
+ case ISC_R_CANCELED:
+ case ISC_R_EOF:
+ ccmsg->result = eresult;
+ goto done;
+ case ISC_R_SUCCESS:
+ if (region == NULL) {
+ ccmsg->result = ISC_R_EOF;
+ goto done;
+ }
+ ccmsg->result = ISC_R_SUCCESS;
+ break;
+ default:
+ ccmsg->result = eresult;
+ goto done;
+ }
+
+ if (!ccmsg->length_received) {
+ if (region->length < sizeof(uint32_t)) {
+ ccmsg->result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+
+ ccmsg->size = ntohl(*(uint32_t *)region->base);
+
+ if (ccmsg->size == 0) {
+ ccmsg->result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+ if (ccmsg->size > ccmsg->maxsize) {
+ ccmsg->result = ISC_R_RANGE;
+ goto done;
+ }
+
+ isc_region_consume(region, sizeof(uint32_t));
+ isc_buffer_allocate(ccmsg->mctx, &ccmsg->buffer, ccmsg->size);
+
+ ccmsg->length_received = true;
+ }
+
+ /*
+ * If there's no more data, wait for more
+ */
+ if (region->length == 0) {
+ return;
+ }
+
+ /* We have some data in the buffer, read it */
+
+ size = ISC_MIN(isc_buffer_availablelength(ccmsg->buffer),
+ region->length);
+ isc_buffer_putmem(ccmsg->buffer, region->base, size);
+ isc_region_consume(region, size);
+
+ if (isc_buffer_usedlength(ccmsg->buffer) == ccmsg->size) {
+ ccmsg->result = ISC_R_SUCCESS;
+ goto done;
+ }
+
+ /* Wait for more data to come */
+ return;
+
+done:
+ isc_nm_pauseread(handle);
+ ccmsg->cb(handle, ccmsg->result, ccmsg->cbarg);
+}
+
+void
+isccc_ccmsg_init(isc_mem_t *mctx, isc_nmhandle_t *handle,
+ isccc_ccmsg_t *ccmsg) {
+ REQUIRE(mctx != NULL);
+ REQUIRE(handle != NULL);
+ REQUIRE(ccmsg != NULL);
+
+ *ccmsg = (isccc_ccmsg_t){
+ .magic = CCMSG_MAGIC,
+ .maxsize = 0xffffffffU, /* Largest message possible. */
+ .mctx = mctx,
+ .handle = handle,
+ .result = ISC_R_UNEXPECTED /* None yet. */
+ };
+}
+
+void
+isccc_ccmsg_setmaxsize(isccc_ccmsg_t *ccmsg, unsigned int maxsize) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ ccmsg->maxsize = maxsize;
+}
+
+void
+isccc_ccmsg_readmessage(isccc_ccmsg_t *ccmsg, isc_nm_cb_t cb, void *cbarg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ if (ccmsg->buffer != NULL) {
+ isc_buffer_free(&ccmsg->buffer);
+ }
+
+ ccmsg->cb = cb;
+ ccmsg->cbarg = cbarg;
+ ccmsg->result = ISC_R_UNEXPECTED; /* unknown right now */
+ ccmsg->length_received = false;
+
+ if (ccmsg->reading) {
+ isc_nm_resumeread(ccmsg->handle);
+ } else {
+ isc_nm_read(ccmsg->handle, recv_data, ccmsg);
+ ccmsg->reading = true;
+ }
+}
+
+void
+isccc_ccmsg_cancelread(isccc_ccmsg_t *ccmsg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ if (ccmsg->reading) {
+ isc_nm_cancelread(ccmsg->handle);
+ ccmsg->reading = false;
+ }
+}
+
+void
+isccc_ccmsg_invalidate(isccc_ccmsg_t *ccmsg) {
+ REQUIRE(VALID_CCMSG(ccmsg));
+
+ ccmsg->magic = 0;
+
+ if (ccmsg->buffer != NULL) {
+ isc_buffer_free(&ccmsg->buffer);
+ }
+}
diff --git a/lib/isccc/include/isccc/alist.h b/lib/isccc/include/isccc/alist.h
new file mode 100644
index 0000000..dc70044
--- /dev/null
+++ b/lib/isccc/include/isccc/alist.h
@@ -0,0 +1,86 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/alist.h */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+
+#include <isccc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+isccc_sexpr_t *
+isccc_alist_create(void);
+
+bool
+isccc_alist_alistp(isccc_sexpr_t *alist);
+
+bool
+isccc_alist_emptyp(isccc_sexpr_t *alist);
+
+isccc_sexpr_t *
+isccc_alist_first(isccc_sexpr_t *alist);
+
+isccc_sexpr_t *
+isccc_alist_assq(isccc_sexpr_t *alist, const char *key);
+
+void
+isccc_alist_delete(isccc_sexpr_t *alist, const char *key);
+
+isccc_sexpr_t *
+isccc_alist_define(isccc_sexpr_t *alist, const char *key, isccc_sexpr_t *value);
+
+isccc_sexpr_t *
+isccc_alist_definestring(isccc_sexpr_t *alist, const char *key,
+ const char *str);
+
+isccc_sexpr_t *
+isccc_alist_definebinary(isccc_sexpr_t *alist, const char *key,
+ isccc_region_t *r);
+
+isccc_sexpr_t *
+isccc_alist_lookup(isccc_sexpr_t *alist, const char *key);
+
+isc_result_t
+isccc_alist_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp);
+
+isc_result_t
+isccc_alist_lookupbinary(isccc_sexpr_t *alist, const char *key,
+ isccc_region_t **r);
+
+void
+isccc_alist_prettyprint(isccc_sexpr_t *sexpr, unsigned int indent,
+ FILE *stream);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/base64.h b/lib/isccc/include/isccc/base64.h
new file mode 100644
index 0000000..641dd4e
--- /dev/null
+++ b/lib/isccc/include/isccc/base64.h
@@ -0,0 +1,79 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/base64.h */
+
+#include <isc/lang.h>
+
+#include <isccc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+isccc_base64_encode(isccc_region_t *source, int wordlength,
+ const char *wordbreak, isccc_region_t *target);
+/*%<
+ * Convert data into base64 encoded text.
+ *
+ * Notes:
+ *\li The base64 encoded text in 'target' will be divided into
+ * words of at most 'wordlength' characters, separated by
+ * the 'wordbreak' string. No parentheses will surround
+ * the text.
+ *
+ * Requires:
+ *\li 'source' is a region containing binary data.
+ *\li 'target' is a text region containing available space.
+ *\li 'wordbreak' points to a null-terminated string of
+ * zero or more whitespace characters.
+ */
+
+isc_result_t
+isccc_base64_decode(const char *cstr, isccc_region_t *target);
+/*%<
+ * Decode a null-terminated base64 string.
+ *
+ * Requires:
+ *\li 'cstr' is non-null.
+ *\li 'target' is a valid region.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS -- the entire decoded representation of 'cstring'
+ * fit in 'target'.
+ *\li #ISC_R_BADBASE64 -- 'cstr' is not a valid base64 encoding.
+ *\li #ISC_R_NOSPACE -- 'target' is not big enough.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/cc.h b/lib/isccc/include/isccc/cc.h
new file mode 100644
index 0000000..f597276
--- /dev/null
+++ b/lib/isccc/include/isccc/cc.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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/cc.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+
+#include <dst/dst.h>
+#include <isccc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*%
+ * The HMAC algorithms supported by isccc_cc_fromwire and
+ * isccc_cc_towire as implemented in DST.
+ */
+#define ISCCC_ALG_UNKNOWN 0
+#define ISCCC_ALG_HMACMD5 DST_ALG_HMACMD5
+#define ISCCC_ALG_HMACSHA1 DST_ALG_HMACSHA1
+#define ISCCC_ALG_HMACSHA224 DST_ALG_HMACSHA224
+#define ISCCC_ALG_HMACSHA256 DST_ALG_HMACSHA256
+#define ISCCC_ALG_HMACSHA384 DST_ALG_HMACSHA384
+#define ISCCC_ALG_HMACSHA512 DST_ALG_HMACSHA512
+
+/*% Maximum Datagram Package */
+#define ISCCC_CC_MAXDGRAMPACKET 4096
+
+/*% Message Type String */
+#define ISCCC_CCMSGTYPE_STRING 0x00
+/*% Message Type Binary Data */
+#define ISCCC_CCMSGTYPE_BINARYDATA 0x01
+/*% Message Type Table */
+#define ISCCC_CCMSGTYPE_TABLE 0x02
+/*% Message Type List */
+#define ISCCC_CCMSGTYPE_LIST 0x03
+
+/*% Send to Wire */
+isc_result_t
+isccc_cc_towire(isccc_sexpr_t *alist, isc_buffer_t **buffer, uint32_t algorithm,
+ isccc_region_t *secret);
+
+/*% Get From Wire */
+isc_result_t
+isccc_cc_fromwire(isccc_region_t *source, isccc_sexpr_t **alistp,
+ uint32_t algorithm, isccc_region_t *secret);
+
+/*% Create Message */
+isc_result_t
+isccc_cc_createmessage(uint32_t version, const char *from, const char *to,
+ uint32_t serial, isccc_time_t now, isccc_time_t expires,
+ isccc_sexpr_t **alistp);
+
+/*% Create Acknowledgment */
+isc_result_t
+isccc_cc_createack(isccc_sexpr_t *message, bool ok, isccc_sexpr_t **ackp);
+
+/*% Is Ack? */
+bool
+isccc_cc_isack(isccc_sexpr_t *message);
+
+/*% Is Reply? */
+bool
+isccc_cc_isreply(isccc_sexpr_t *message);
+
+/*% Create Response */
+isc_result_t
+isccc_cc_createresponse(isccc_sexpr_t *message, isccc_time_t now,
+ isccc_time_t expires, isccc_sexpr_t **alistp);
+
+/*% Define String */
+isccc_sexpr_t *
+isccc_cc_definestring(isccc_sexpr_t *alist, const char *key, const char *str);
+
+/*% Define uint 32 */
+isccc_sexpr_t *
+isccc_cc_defineuint32(isccc_sexpr_t *alist, const char *key, uint32_t i);
+
+/*% Lookup String */
+isc_result_t
+isccc_cc_lookupstring(isccc_sexpr_t *alist, const char *key, char **strp);
+
+/*% Lookup uint 32 */
+isc_result_t
+isccc_cc_lookupuint32(isccc_sexpr_t *alist, const char *key, uint32_t *uintp);
+
+/*% Create Symbol Table */
+isc_result_t
+isccc_cc_createsymtab(isccc_symtab_t **symtabp);
+
+/*% Clean up Symbol Table */
+void
+isccc_cc_cleansymtab(isccc_symtab_t *symtab, isccc_time_t now);
+
+/*% Check for Duplicates */
+isc_result_t
+isccc_cc_checkdup(isccc_symtab_t *symtab, isccc_sexpr_t *message,
+ isccc_time_t now);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/ccmsg.h b/lib/isccc/include/isccc/ccmsg.h
new file mode 100644
index 0000000..a648226
--- /dev/null
+++ b/lib/isccc/include/isccc/ccmsg.h
@@ -0,0 +1,137 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/ccmsg.h */
+
+#include <inttypes.h>
+
+#include <isc/buffer.h>
+#include <isc/lang.h>
+#include <isc/netmgr.h>
+#include <isc/sockaddr.h>
+
+/*% ISCCC Message Structure */
+typedef struct isccc_ccmsg {
+ /* private (don't touch!) */
+ unsigned int magic;
+ uint32_t size;
+ bool length_received;
+ isc_buffer_t *buffer;
+ unsigned int maxsize;
+ isc_mem_t *mctx;
+ isc_nmhandle_t *handle;
+ isc_nm_cb_t cb;
+ void *cbarg;
+ bool reading;
+ /* public (read-only) */
+ isc_result_t result;
+} isccc_ccmsg_t;
+
+ISC_LANG_BEGINDECLS
+
+void
+isccc_ccmsg_init(isc_mem_t *mctx, isc_nmhandle_t *handle, isccc_ccmsg_t *ccmsg);
+/*%
+ * Associate a cc message state with a given memory context and
+ * netmgr handle. (Note that the caller must hold a reference to
+ * the handle during asynchronous ccmsg operations; the ccmsg code
+ * does not hold the reference itself.)
+ *
+ * Requires:
+ *
+ *\li "mctx" be a valid memory context.
+ *
+ *\li "handle" be a netmgr handle for a stream socket.
+ *
+ *\li "ccmsg" be non-NULL and an uninitialized or invalidated structure.
+ *
+ * Ensures:
+ *
+ *\li "ccmsg" is a valid structure.
+ */
+
+void
+isccc_ccmsg_setmaxsize(isccc_ccmsg_t *ccmsg, unsigned int maxsize);
+/*%
+ * Set the maximum packet size to "maxsize"
+ *
+ * Requires:
+ *
+ *\li "ccmsg" be valid.
+ *
+ *\li 512 <= "maxsize" <= 4294967296
+ */
+
+void
+isccc_ccmsg_readmessage(isccc_ccmsg_t *ccmsg, isc_nm_cb_t cb, void *cbarg);
+/*%
+ * Schedule an event to be delivered when a command channel message is
+ * readable, or when an error occurs on the socket.
+ *
+ * Requires:
+ *
+ *\li "ccmsg" be valid.
+ *
+ * Notes:
+ *
+ *\li The event delivered is a fully generic event. It will contain no
+ * actual data. The sender will be a pointer to the isccc_ccmsg_t.
+ * The result code inside that structure should be checked to see
+ * what the final result was.
+ */
+
+void
+isccc_ccmsg_cancelread(isccc_ccmsg_t *ccmsg);
+/*%
+ * Cancel a readmessage() call. The event will still be posted with a
+ * CANCELED result code.
+ *
+ * Requires:
+ *
+ *\li "ccmsg" be valid.
+ */
+
+void
+isccc_ccmsg_invalidate(isccc_ccmsg_t *ccmsg);
+/*%
+ * Clean up all allocated state, and invalidate the structure.
+ *
+ * Requires:
+ *
+ *\li "ccmsg" be valid.
+ *
+ * Ensures:
+ *
+ *\li "ccmsg" is invalidated and disassociated with all memory contexts,
+ * sockets, etc.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/events.h b/lib/isccc/include/isccc/events.h
new file mode 100644
index 0000000..0461a8a
--- /dev/null
+++ b/lib/isccc/include/isccc/events.h
@@ -0,0 +1,43 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/events.h */
+
+#include <isc/eventclass.h>
+
+/*%
+ * Registry of ISCCC event numbers.
+ */
+
+#define ISCCC_EVENT_CCMSG (ISC_EVENTCLASS_ISCCC + 0)
+
+#define ISCCC_EVENT_FIRSTEVENT (ISC_EVENTCLASS_ISCCC + 0)
+#define ISCCC_EVENT_LASTEVENT (ISC_EVENTCLASS_ISCCC + 65535)
diff --git a/lib/isccc/include/isccc/sexpr.h b/lib/isccc/include/isccc/sexpr.h
new file mode 100644
index 0000000..133d06d
--- /dev/null
+++ b/lib/isccc/include/isccc/sexpr.h
@@ -0,0 +1,119 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/sexpr.h */
+
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <isc/lang.h>
+
+#include <isccc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+/*% dotted pair structure */
+struct isccc_dottedpair {
+ isccc_sexpr_t *car;
+ isccc_sexpr_t *cdr;
+};
+
+/*% iscc_sexpr structure */
+struct isccc_sexpr {
+ unsigned int type;
+ union {
+ char *as_string;
+ isccc_dottedpair_t as_dottedpair;
+ isccc_region_t as_region;
+ } value;
+};
+
+#define ISCCC_SEXPRTYPE_NONE 0x00 /*%< Illegal. */
+#define ISCCC_SEXPRTYPE_T 0x01
+#define ISCCC_SEXPRTYPE_STRING 0x02
+#define ISCCC_SEXPRTYPE_DOTTEDPAIR 0x03
+#define ISCCC_SEXPRTYPE_BINARY 0x04
+
+#define ISCCC_SEXPR_CAR(s) (s)->value.as_dottedpair.car
+#define ISCCC_SEXPR_CDR(s) (s)->value.as_dottedpair.cdr
+
+isccc_sexpr_t *
+isccc_sexpr_cons(isccc_sexpr_t *car, isccc_sexpr_t *cdr);
+
+isccc_sexpr_t *
+isccc_sexpr_tconst(void);
+
+isccc_sexpr_t *
+isccc_sexpr_fromstring(const char *str);
+
+isccc_sexpr_t *
+isccc_sexpr_frombinary(const isccc_region_t *region);
+
+void
+isccc_sexpr_free(isccc_sexpr_t **sexprp);
+
+void
+isccc_sexpr_print(isccc_sexpr_t *sexpr, FILE *stream);
+
+isccc_sexpr_t *
+isccc_sexpr_car(isccc_sexpr_t *list);
+
+isccc_sexpr_t *
+isccc_sexpr_cdr(isccc_sexpr_t *list);
+
+void
+isccc_sexpr_setcar(isccc_sexpr_t *pair, isccc_sexpr_t *car);
+
+void
+isccc_sexpr_setcdr(isccc_sexpr_t *pair, isccc_sexpr_t *cdr);
+
+isccc_sexpr_t *
+isccc_sexpr_addtolist(isccc_sexpr_t **l1p, isccc_sexpr_t *l2);
+
+bool
+isccc_sexpr_listp(isccc_sexpr_t *sexpr);
+
+bool
+isccc_sexpr_emptyp(isccc_sexpr_t *sexpr);
+
+bool
+isccc_sexpr_stringp(isccc_sexpr_t *sexpr);
+
+bool
+isccc_sexpr_binaryp(isccc_sexpr_t *sexpr);
+
+char *
+isccc_sexpr_tostring(isccc_sexpr_t *sexpr);
+
+isccc_region_t *
+isccc_sexpr_tobinary(isccc_sexpr_t *sexpr);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/symtab.h b/lib/isccc/include/isccc/symtab.h
new file mode 100644
index 0000000..a156c06
--- /dev/null
+++ b/lib/isccc/include/isccc/symtab.h
@@ -0,0 +1,133 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*****
+***** Module Info
+*****/
+
+/*! \file isccc/symtab.h
+ * \brief
+ * Provides a simple memory-based symbol table.
+ *
+ * Keys are C strings. A type may be specified when looking up,
+ * defining, or undefining. A type value of 0 means "match any type";
+ * any other value will only match the given type.
+ *
+ * It's possible that a client will attempt to define a <key, type,
+ * value> tuple when a tuple with the given key and type already
+ * exists in the table. What to do in this case is specified by the
+ * client. Possible policies are:
+ *
+ *\li isccc_symexists_reject Disallow the define, returning #ISC_R_EXISTS
+ *\li isccc_symexists_replace Replace the old value with the new. The
+ * undefine action (if provided) will be called
+ * with the old <key, type, value> tuple.
+ *\li isccc_symexists_add Add the new tuple, leaving the old tuple in
+ * the table. Subsequent lookups will retrieve
+ * the most-recently-defined tuple.
+ *
+ * A lookup of a key using type 0 will return the most-recently
+ * defined symbol with that key. An undefine of a key using type 0
+ * will undefine the most-recently defined symbol with that key.
+ * Trying to define a key with type 0 is illegal.
+ *
+ * The symbol table library does not make a copy the key field, so the
+ * caller must ensure that any key it passes to isccc_symtab_define()
+ * will not change until it calls isccc_symtab_undefine() or
+ * isccc_symtab_destroy().
+ *
+ * A user-specified action will be called (if provided) when a symbol
+ * is undefined. It can be used to free memory associated with keys
+ * and/or values.
+ */
+
+/***
+ *** Imports.
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/lang.h>
+
+#include <isccc/types.h>
+
+/***
+ *** Symbol Tables.
+ ***/
+
+typedef union isccc_symvalue {
+ void *as_pointer;
+ int as_integer;
+ unsigned int as_uinteger;
+} isccc_symvalue_t;
+
+typedef void (*isccc_symtabundefaction_t)(char *key, unsigned int type,
+ isccc_symvalue_t value,
+ void *userarg);
+
+typedef bool (*isccc_symtabforeachaction_t)(char *key, unsigned int type,
+ isccc_symvalue_t value,
+ void *userarg);
+
+typedef enum {
+ isccc_symexists_reject = 0,
+ isccc_symexists_replace = 1,
+ isccc_symexists_add = 2
+} isccc_symexists_t;
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+isccc_symtab_create(unsigned int size,
+ isccc_symtabundefaction_t undefine_action,
+ void *undefine_arg, bool case_sensitive,
+ isccc_symtab_t **symtabp);
+
+void
+isccc_symtab_destroy(isccc_symtab_t **symtabp);
+
+isc_result_t
+isccc_symtab_lookup(isccc_symtab_t *symtab, const char *key, unsigned int type,
+ isccc_symvalue_t *value);
+
+isc_result_t
+isccc_symtab_define(isccc_symtab_t *symtab, char *key, unsigned int type,
+ isccc_symvalue_t value, isccc_symexists_t exists_policy);
+
+isc_result_t
+isccc_symtab_undefine(isccc_symtab_t *symtab, const char *key,
+ unsigned int type);
+
+void
+isccc_symtab_foreach(isccc_symtab_t *symtab, isccc_symtabforeachaction_t action,
+ void *arg);
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccc/include/isccc/symtype.h b/lib/isccc/include/isccc/symtype.h
new file mode 100644
index 0000000..8e67eda
--- /dev/null
+++ b/lib/isccc/include/isccc/symtype.h
@@ -0,0 +1,37 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/symtype.h */
+
+#define ISCCC_SYMTYPE_ZONESTATS 0x0001
+#define ISCCC_SYMTYPE_CCDUP 0x0002
+#define ISCCC_SYMTYPE_TELLSERVICE 0x0003
+#define ISCCC_SYMTYPE_TELLRESPONSE 0x0004
diff --git a/lib/isccc/include/isccc/types.h b/lib/isccc/include/isccc/types.h
new file mode 100644
index 0000000..f2032a7
--- /dev/null
+++ b/lib/isccc/include/isccc/types.h
@@ -0,0 +1,52 @@
+/*
+ * Portions 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) 2001 Nominum, 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 NOMINUM 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
+
+/*! \file isccc/types.h */
+
+#include <inttypes.h>
+
+#include <isc/result.h>
+
+/*% isccc_time_t typedef */
+typedef uint32_t isccc_time_t;
+
+/*% isccc_sexpr_t typedef */
+typedef struct isccc_sexpr isccc_sexpr_t;
+/*% isccc_dottedpair_t typedef */
+typedef struct isccc_dottedpair isccc_dottedpair_t;
+/*% isccc_symtab_t typedef */
+typedef struct isccc_symtab isccc_symtab_t;
+
+/*% iscc region structure */
+typedef struct isccc_region {
+ unsigned char *rstart;
+ unsigned char *rend;
+} isccc_region_t;
diff --git a/lib/isccc/include/isccc/util.h b/lib/isccc/include/isccc/util.h
new file mode 100644
index 0000000..546b633
--- /dev/null
+++ b/lib/isccc/include/isccc/util.h
@@ -0,0 +1,208 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <isc/util.h>
+
+/*! \file isccc/util.h
+ * \brief
+ * Macros for dealing with unaligned numbers.
+ *
+ * \note no side effects are allowed when invoking these macros!
+ */
+
+#define GET8(v, w) \
+ do { \
+ v = *w; \
+ w++; \
+ } while (0)
+
+#define GET16(v, w) \
+ do { \
+ v = (unsigned int)w[0] << 8; \
+ v |= (unsigned int)w[1]; \
+ w += 2; \
+ } while (0)
+
+#define GET24(v, w) \
+ do { \
+ v = (unsigned int)w[0] << 16; \
+ v |= (unsigned int)w[1] << 8; \
+ v |= (unsigned int)w[2]; \
+ w += 3; \
+ } while (0)
+
+#define GET32(v, w) \
+ do { \
+ v = (unsigned int)w[0] << 24; \
+ v |= (unsigned int)w[1] << 16; \
+ v |= (unsigned int)w[2] << 8; \
+ v |= (unsigned int)w[3]; \
+ w += 4; \
+ } while (0)
+
+#define GET64(v, w) \
+ do { \
+ v = (uint64_t)w[0] << 56; \
+ v |= (uint64_t)w[1] << 48; \
+ v |= (uint64_t)w[2] << 40; \
+ v |= (uint64_t)w[3] << 32; \
+ v |= (uint64_t)w[4] << 24; \
+ v |= (uint64_t)w[5] << 16; \
+ v |= (uint64_t)w[6] << 8; \
+ v |= (uint64_t)w[7]; \
+ w += 8; \
+ } while (0)
+
+#define GETC16(v, w, d) \
+ do { \
+ GET8(v, w); \
+ if (v == 0) { \
+ d = ISCCC_TRUE; \
+ } else { \
+ d = ISCCC_FALSE; \
+ if (v == 255) \
+ GET16(v, w); \
+ } \
+ } while (0)
+
+#define GETC32(v, w) \
+ do { \
+ GET24(v, w); \
+ if (v == 0xffffffu) { \
+ GET32(v, w); \
+ } \
+ } while (0)
+
+#define GET_OFFSET(v, w) GET32(v, w)
+
+#define GET_MEM(v, c, w) \
+ do { \
+ memmove(v, w, c); \
+ w += c; \
+ } while (0)
+
+#define GET_TYPE(v, w) \
+ do { \
+ GET8(v, w); \
+ if (v > 127) { \
+ if (v < 255) { \
+ v = ((v & 0x7f) << 16) | ISCCC_RDATATYPE_SIG; \
+ } else { \
+ GET32(v, w); \
+ } \
+ } \
+ } while (0)
+
+#define PUT8(v, w) \
+ do { \
+ *w = (v & 0x000000ffU); \
+ w++; \
+ } while (0)
+
+#define PUT16(v, w) \
+ do { \
+ w[0] = (v & 0x0000ff00U) >> 8; \
+ w[1] = (v & 0x000000ffU); \
+ w += 2; \
+ } while (0)
+
+#define PUT24(v, w) \
+ do { \
+ w[0] = (v & 0x00ff0000U) >> 16; \
+ w[1] = (v & 0x0000ff00U) >> 8; \
+ w[2] = (v & 0x000000ffU); \
+ w += 3; \
+ } while (0)
+
+#define PUT32(v, w) \
+ do { \
+ w[0] = (v & 0xff000000U) >> 24; \
+ w[1] = (v & 0x00ff0000U) >> 16; \
+ w[2] = (v & 0x0000ff00U) >> 8; \
+ w[3] = (v & 0x000000ffU); \
+ w += 4; \
+ } while (0)
+
+#define PUT64(v, w) \
+ do { \
+ w[0] = (v & 0xff00000000000000ULL) >> 56; \
+ w[1] = (v & 0x00ff000000000000ULL) >> 48; \
+ w[2] = (v & 0x0000ff0000000000ULL) >> 40; \
+ w[3] = (v & 0x000000ff00000000ULL) >> 32; \
+ w[4] = (v & 0x00000000ff000000ULL) >> 24; \
+ w[5] = (v & 0x0000000000ff0000ULL) >> 16; \
+ w[6] = (v & 0x000000000000ff00ULL) >> 8; \
+ w[7] = (v & 0x00000000000000ffULL); \
+ w += 8; \
+ } while (0)
+
+#define PUTC16(v, w) \
+ do { \
+ if (v > 0 && v < 255) { \
+ PUT8(v, w); \
+ } else { \
+ PUT8(255, w); \
+ PUT16(v, w); \
+ } \
+ } while (0)
+
+#define PUTC32(v, w) \
+ do { \
+ if (v < 0xffffffU) { \
+ PUT24(v, w); \
+ } else { \
+ PUT24(0xffffffU, w); \
+ PUT32(v, w); \
+ } \
+ } while (0)
+
+#define PUT_OFFSET(v, w) PUT32(v, w)
+
+#include <string.h>
+
+#define PUT_MEM(s, c, w) \
+ do { \
+ memmove(w, s, c); \
+ w += c; \
+ } while (0)
+
+/*
+ * Regions.
+ */
+#define REGION_SIZE(r) ((unsigned int)((r).rend - (r).rstart))
+#define REGION_EMPTY(r) ((r).rstart == (r).rend)
+#define REGION_FROMSTRING(r, s) \
+ do { \
+ (r).rstart = (unsigned char *)s; \
+ (r).rend = (r).rstart + strlen(s); \
+ } while (0)
diff --git a/lib/isccc/sexpr.c b/lib/isccc/sexpr.c
new file mode 100644
index 0000000..62c80c4
--- /dev/null
+++ b/lib/isccc/sexpr.c
@@ -0,0 +1,317 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/assertions.h>
+#include <isc/print.h>
+
+#include <isccc/sexpr.h>
+#include <isccc/util.h>
+
+static isccc_sexpr_t sexpr_t = { ISCCC_SEXPRTYPE_T, { NULL } };
+
+#define CAR(s) (s)->value.as_dottedpair.car
+#define CDR(s) (s)->value.as_dottedpair.cdr
+
+isccc_sexpr_t *
+isccc_sexpr_cons(isccc_sexpr_t *car, isccc_sexpr_t *cdr) {
+ isccc_sexpr_t *sexpr;
+
+ sexpr = malloc(sizeof(*sexpr));
+ if (sexpr == NULL) {
+ return (NULL);
+ }
+ sexpr->type = ISCCC_SEXPRTYPE_DOTTEDPAIR;
+ CAR(sexpr) = car;
+ CDR(sexpr) = cdr;
+
+ return (sexpr);
+}
+
+isccc_sexpr_t *
+isccc_sexpr_tconst(void) {
+ return (&sexpr_t);
+}
+
+isccc_sexpr_t *
+isccc_sexpr_fromstring(const char *str) {
+ isccc_sexpr_t *sexpr;
+
+ sexpr = malloc(sizeof(*sexpr));
+ if (sexpr == NULL) {
+ return (NULL);
+ }
+ sexpr->type = ISCCC_SEXPRTYPE_STRING;
+ sexpr->value.as_string = strdup(str);
+ if (sexpr->value.as_string == NULL) {
+ free(sexpr);
+ return (NULL);
+ }
+
+ return (sexpr);
+}
+
+isccc_sexpr_t *
+isccc_sexpr_frombinary(const isccc_region_t *region) {
+ isccc_sexpr_t *sexpr;
+ unsigned int region_size;
+
+ sexpr = malloc(sizeof(*sexpr));
+ if (sexpr == NULL) {
+ return (NULL);
+ }
+ sexpr->type = ISCCC_SEXPRTYPE_BINARY;
+ region_size = REGION_SIZE(*region);
+ /*
+ * We add an extra byte when we malloc so we can NUL terminate
+ * the binary data. This allows the caller to use it as a C
+ * string. It's up to the caller to ensure this is safe. We don't
+ * add 1 to the length of the binary region, because the NUL is
+ * not part of the binary data.
+ */
+ sexpr->value.as_region.rstart = malloc(region_size + 1);
+ if (sexpr->value.as_region.rstart == NULL) {
+ free(sexpr);
+ return (NULL);
+ }
+ sexpr->value.as_region.rend = sexpr->value.as_region.rstart +
+ region_size;
+ memmove(sexpr->value.as_region.rstart, region->rstart, region_size);
+ /*
+ * NUL terminate.
+ */
+ sexpr->value.as_region.rstart[region_size] = '\0';
+
+ return (sexpr);
+}
+
+void
+isccc_sexpr_free(isccc_sexpr_t **sexprp) {
+ isccc_sexpr_t *sexpr;
+ isccc_sexpr_t *item;
+
+ sexpr = *sexprp;
+ *sexprp = NULL;
+ if (sexpr == NULL) {
+ return;
+ }
+ switch (sexpr->type) {
+ case ISCCC_SEXPRTYPE_STRING:
+ free(sexpr->value.as_string);
+ break;
+ case ISCCC_SEXPRTYPE_DOTTEDPAIR:
+ item = CAR(sexpr);
+ if (item != NULL) {
+ isccc_sexpr_free(&item);
+ }
+ item = CDR(sexpr);
+ if (item != NULL) {
+ isccc_sexpr_free(&item);
+ }
+ break;
+ case ISCCC_SEXPRTYPE_BINARY:
+ free(sexpr->value.as_region.rstart);
+ break;
+ }
+ free(sexpr);
+}
+
+static bool
+printable(isccc_region_t *r) {
+ unsigned char *curr;
+
+ curr = r->rstart;
+ while (curr != r->rend) {
+ if (!isprint(*curr)) {
+ return (false);
+ }
+ curr++;
+ }
+
+ return (true);
+}
+
+void
+isccc_sexpr_print(isccc_sexpr_t *sexpr, FILE *stream) {
+ isccc_sexpr_t *cdr;
+ unsigned int size, i;
+ unsigned char *curr;
+
+ if (sexpr == NULL) {
+ fprintf(stream, "nil");
+ return;
+ }
+
+ switch (sexpr->type) {
+ case ISCCC_SEXPRTYPE_T:
+ fprintf(stream, "t");
+ break;
+ case ISCCC_SEXPRTYPE_STRING:
+ fprintf(stream, "\"%s\"", sexpr->value.as_string);
+ break;
+ case ISCCC_SEXPRTYPE_DOTTEDPAIR:
+ fprintf(stream, "(");
+ do {
+ isccc_sexpr_print(CAR(sexpr), stream);
+ cdr = CDR(sexpr);
+ if (cdr != NULL) {
+ fprintf(stream, " ");
+ if (cdr->type != ISCCC_SEXPRTYPE_DOTTEDPAIR) {
+ fprintf(stream, ". ");
+ isccc_sexpr_print(cdr, stream);
+ cdr = NULL;
+ }
+ }
+ sexpr = cdr;
+ } while (sexpr != NULL);
+ fprintf(stream, ")");
+ break;
+ case ISCCC_SEXPRTYPE_BINARY:
+ size = REGION_SIZE(sexpr->value.as_region);
+ curr = sexpr->value.as_region.rstart;
+ if (printable(&sexpr->value.as_region)) {
+ fprintf(stream, "'%.*s'", (int)size, curr);
+ } else {
+ fprintf(stream, "0x");
+ for (i = 0; i < size; i++) {
+ fprintf(stream, "%02x", *curr++);
+ }
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+isccc_sexpr_t *
+isccc_sexpr_car(isccc_sexpr_t *list) {
+ REQUIRE(list->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+
+ return (CAR(list));
+}
+
+isccc_sexpr_t *
+isccc_sexpr_cdr(isccc_sexpr_t *list) {
+ REQUIRE(list->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+
+ return (CDR(list));
+}
+
+void
+isccc_sexpr_setcar(isccc_sexpr_t *pair, isccc_sexpr_t *car) {
+ REQUIRE(pair->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+
+ CAR(pair) = car;
+}
+
+void
+isccc_sexpr_setcdr(isccc_sexpr_t *pair, isccc_sexpr_t *cdr) {
+ REQUIRE(pair->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+
+ CDR(pair) = cdr;
+}
+
+isccc_sexpr_t *
+isccc_sexpr_addtolist(isccc_sexpr_t **l1p, isccc_sexpr_t *l2) {
+ isccc_sexpr_t *last, *elt, *l1;
+
+ REQUIRE(l1p != NULL);
+ l1 = *l1p;
+ REQUIRE(l1 == NULL || l1->type == ISCCC_SEXPRTYPE_DOTTEDPAIR);
+
+ elt = isccc_sexpr_cons(l2, NULL);
+ if (elt == NULL) {
+ return (NULL);
+ }
+ if (l1 == NULL) {
+ *l1p = elt;
+ return (elt);
+ }
+ for (last = l1; CDR(last) != NULL; last = CDR(last)) {
+ /* Nothing */
+ }
+ CDR(last) = elt;
+
+ return (elt);
+}
+
+bool
+isccc_sexpr_listp(isccc_sexpr_t *sexpr) {
+ if (sexpr == NULL || sexpr->type == ISCCC_SEXPRTYPE_DOTTEDPAIR) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+isccc_sexpr_emptyp(isccc_sexpr_t *sexpr) {
+ if (sexpr == NULL) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+isccc_sexpr_stringp(isccc_sexpr_t *sexpr) {
+ if (sexpr != NULL && sexpr->type == ISCCC_SEXPRTYPE_STRING) {
+ return (true);
+ }
+ return (false);
+}
+
+bool
+isccc_sexpr_binaryp(isccc_sexpr_t *sexpr) {
+ if (sexpr != NULL && sexpr->type == ISCCC_SEXPRTYPE_BINARY) {
+ return (true);
+ }
+ return (false);
+}
+
+char *
+isccc_sexpr_tostring(isccc_sexpr_t *sexpr) {
+ REQUIRE(sexpr != NULL && (sexpr->type == ISCCC_SEXPRTYPE_STRING ||
+ sexpr->type == ISCCC_SEXPRTYPE_BINARY));
+
+ if (sexpr->type == ISCCC_SEXPRTYPE_BINARY) {
+ return ((char *)sexpr->value.as_region.rstart);
+ }
+ return (sexpr->value.as_string);
+}
+
+isccc_region_t *
+isccc_sexpr_tobinary(isccc_sexpr_t *sexpr) {
+ REQUIRE(sexpr != NULL && sexpr->type == ISCCC_SEXPRTYPE_BINARY);
+ return (&sexpr->value.as_region);
+}
diff --git a/lib/isccc/symtab.c b/lib/isccc/symtab.c
new file mode 100644
index 0000000..62e4614
--- /dev/null
+++ b/lib/isccc/symtab.c
@@ -0,0 +1,293 @@
+/*
+ * 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) 2001 Nominum, 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 NOMINUM 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 <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/assertions.h>
+#include <isc/magic.h>
+#include <isc/result.h>
+#include <isc/string.h>
+
+#include <isccc/symtab.h>
+#include <isccc/util.h>
+
+typedef struct elt {
+ char *key;
+ unsigned int type;
+ isccc_symvalue_t value;
+ ISC_LINK(struct elt) link;
+} elt_t;
+
+typedef ISC_LIST(elt_t) eltlist_t;
+
+#define SYMTAB_MAGIC ISC_MAGIC('S', 'y', 'm', 'T')
+#define VALID_SYMTAB(st) ISC_MAGIC_VALID(st, SYMTAB_MAGIC)
+
+struct isccc_symtab {
+ unsigned int magic;
+ unsigned int size;
+ eltlist_t *table;
+ isccc_symtabundefaction_t undefine_action;
+ void *undefine_arg;
+ bool case_sensitive;
+};
+
+isc_result_t
+isccc_symtab_create(unsigned int size,
+ isccc_symtabundefaction_t undefine_action,
+ void *undefine_arg, bool case_sensitive,
+ isccc_symtab_t **symtabp) {
+ isccc_symtab_t *symtab;
+ unsigned int i;
+
+ REQUIRE(symtabp != NULL && *symtabp == NULL);
+ REQUIRE(size > 0); /* Should be prime. */
+
+ symtab = malloc(sizeof(*symtab));
+ if (symtab == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ symtab->table = malloc(size * sizeof(eltlist_t));
+ if (symtab->table == NULL) {
+ free(symtab);
+ return (ISC_R_NOMEMORY);
+ }
+ for (i = 0; i < size; i++) {
+ ISC_LIST_INIT(symtab->table[i]);
+ }
+ symtab->size = size;
+ symtab->undefine_action = undefine_action;
+ symtab->undefine_arg = undefine_arg;
+ symtab->case_sensitive = case_sensitive;
+ symtab->magic = SYMTAB_MAGIC;
+
+ *symtabp = symtab;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+free_elt(isccc_symtab_t *symtab, unsigned int bucket, elt_t *elt) {
+ ISC_LIST_UNLINK(symtab->table[bucket], elt, link);
+ if (symtab->undefine_action != NULL) {
+ (symtab->undefine_action)(elt->key, elt->type, elt->value,
+ symtab->undefine_arg);
+ }
+ free(elt);
+}
+
+void
+isccc_symtab_destroy(isccc_symtab_t **symtabp) {
+ isccc_symtab_t *symtab;
+ unsigned int i;
+ elt_t *elt, *nelt;
+
+ REQUIRE(symtabp != NULL);
+ symtab = *symtabp;
+ *symtabp = NULL;
+ REQUIRE(VALID_SYMTAB(symtab));
+
+ for (i = 0; i < symtab->size; i++) {
+ for (elt = ISC_LIST_HEAD(symtab->table[i]); elt != NULL;
+ elt = nelt)
+ {
+ nelt = ISC_LIST_NEXT(elt, link);
+ free_elt(symtab, i, elt);
+ }
+ }
+ free(symtab->table);
+ symtab->magic = 0;
+ free(symtab);
+}
+
+static unsigned int
+hash(const char *key, bool case_sensitive) {
+ const char *s;
+ unsigned int h = 0;
+ unsigned int g;
+ int c;
+
+ /*
+ * P. J. Weinberger's hash function, adapted from p. 436 of
+ * _Compilers: Principles, Techniques, and Tools_, Aho, Sethi
+ * and Ullman, Addison-Wesley, 1986, ISBN 0-201-10088-6.
+ */
+
+ if (case_sensitive) {
+ for (s = key; *s != '\0'; s++) {
+ h = (h << 4) + *s;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ } else {
+ for (s = key; *s != '\0'; s++) {
+ c = *s;
+ c = tolower((unsigned char)c);
+ h = (h << 4) + c;
+ if ((g = (h & 0xf0000000)) != 0) {
+ h = h ^ (g >> 24);
+ h = h ^ g;
+ }
+ }
+ }
+
+ return (h);
+}
+
+#define FIND(s, k, t, b, e) \
+ b = hash((k), (s)->case_sensitive) % (s)->size; \
+ if ((s)->case_sensitive) { \
+ for (e = ISC_LIST_HEAD((s)->table[b]); e != NULL; \
+ e = ISC_LIST_NEXT(e, link)) \
+ { \
+ if (((t) == 0 || e->type == (t)) && \
+ strcmp(e->key, (k)) == 0) \
+ break; \
+ } \
+ } else { \
+ for (e = ISC_LIST_HEAD((s)->table[b]); e != NULL; \
+ e = ISC_LIST_NEXT(e, link)) \
+ { \
+ if (((t) == 0 || e->type == (t)) && \
+ strcasecmp(e->key, (k)) == 0) \
+ break; \
+ } \
+ }
+
+isc_result_t
+isccc_symtab_lookup(isccc_symtab_t *symtab, const char *key, unsigned int type,
+ isccc_symvalue_t *value) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (elt == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (value != NULL) {
+ *value = elt->value;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccc_symtab_define(isccc_symtab_t *symtab, char *key, unsigned int type,
+ isccc_symvalue_t value, isccc_symexists_t exists_policy) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+ REQUIRE(type != 0);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (exists_policy != isccc_symexists_add && elt != NULL) {
+ if (exists_policy == isccc_symexists_reject) {
+ return (ISC_R_EXISTS);
+ }
+ INSIST(exists_policy == isccc_symexists_replace);
+ ISC_LIST_UNLINK(symtab->table[bucket], elt, link);
+ if (symtab->undefine_action != NULL) {
+ (symtab->undefine_action)(elt->key, elt->type,
+ elt->value,
+ symtab->undefine_arg);
+ }
+ } else {
+ elt = malloc(sizeof(*elt));
+ if (elt == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+ ISC_LINK_INIT(elt, link);
+ }
+
+ elt->key = key;
+ elt->type = type;
+ elt->value = value;
+
+ /*
+ * We prepend so that the most recent definition will be found.
+ */
+ ISC_LIST_PREPEND(symtab->table[bucket], elt, link);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccc_symtab_undefine(isccc_symtab_t *symtab, const char *key,
+ unsigned int type) {
+ unsigned int bucket;
+ elt_t *elt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(key != NULL);
+
+ FIND(symtab, key, type, bucket, elt);
+
+ if (elt == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ free_elt(symtab, bucket, elt);
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+isccc_symtab_foreach(isccc_symtab_t *symtab, isccc_symtabforeachaction_t action,
+ void *arg) {
+ unsigned int i;
+ elt_t *elt, *nelt;
+
+ REQUIRE(VALID_SYMTAB(symtab));
+ REQUIRE(action != NULL);
+
+ for (i = 0; i < symtab->size; i++) {
+ for (elt = ISC_LIST_HEAD(symtab->table[i]); elt != NULL;
+ elt = nelt)
+ {
+ nelt = ISC_LIST_NEXT(elt, link);
+ if ((action)(elt->key, elt->type, elt->value, arg)) {
+ free_elt(symtab, i, elt);
+ }
+ }
+ }
+}
diff --git a/lib/isccfg/Makefile.am b/lib/isccfg/Makefile.am
new file mode 100644
index 0000000..0c95c4f
--- /dev/null
+++ b/lib/isccfg/Makefile.am
@@ -0,0 +1,37 @@
+include $(top_srcdir)/Makefile.top
+
+lib_LTLIBRARIES = libisccfg.la
+
+libisccfg_ladir = $(includedir)/isccfg
+libisccfg_la_HEADERS = \
+ include/isccfg/aclconf.h \
+ include/isccfg/cfg.h \
+ include/isccfg/duration.h \
+ include/isccfg/grammar.h \
+ include/isccfg/kaspconf.h \
+ include/isccfg/log.h \
+ include/isccfg/namedconf.h
+
+libisccfg_la_SOURCES = \
+ $(libisccfg_la_HEADERS) \
+ aclconf.c \
+ dnsconf.c \
+ duration.c \
+ kaspconf.c \
+ log.c \
+ namedconf.c \
+ parser.c
+
+libisccfg_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS)
+
+libisccfg_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libisccfg_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
diff --git a/lib/isccfg/Makefile.in b/lib/isccfg/Makefile.in
new file mode 100644
index 0000000..38f6b93
--- /dev/null
+++ b/lib/isccfg/Makefile.in
@@ -0,0 +1,967 @@
+# 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
+
+subdir = lib/isccfg
+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 $(libisccfg_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)$(libisccfg_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+libisccfg_la_DEPENDENCIES = $(LIBDNS_LIBS) $(LIBISC_LIBS)
+am__objects_1 =
+am_libisccfg_la_OBJECTS = $(am__objects_1) libisccfg_la-aclconf.lo \
+ libisccfg_la-dnsconf.lo libisccfg_la-duration.lo \
+ libisccfg_la-kaspconf.lo libisccfg_la-log.lo \
+ libisccfg_la-namedconf.lo libisccfg_la-parser.lo
+libisccfg_la_OBJECTS = $(am_libisccfg_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 =
+libisccfg_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libisccfg_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)/libisccfg_la-aclconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-dnsconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-duration.Plo \
+ ./$(DEPDIR)/libisccfg_la-kaspconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-log.Plo \
+ ./$(DEPDIR)/libisccfg_la-namedconf.Plo \
+ ./$(DEPDIR)/libisccfg_la-parser.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 = $(libisccfg_la_SOURCES)
+DIST_SOURCES = $(libisccfg_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libisccfg_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 = libisccfg.la
+libisccfg_ladir = $(includedir)/isccfg
+libisccfg_la_HEADERS = \
+ include/isccfg/aclconf.h \
+ include/isccfg/cfg.h \
+ include/isccfg/duration.h \
+ include/isccfg/grammar.h \
+ include/isccfg/kaspconf.h \
+ include/isccfg/log.h \
+ include/isccfg/namedconf.h
+
+libisccfg_la_SOURCES = \
+ $(libisccfg_la_HEADERS) \
+ aclconf.c \
+ dnsconf.c \
+ duration.c \
+ kaspconf.c \
+ log.c \
+ namedconf.c \
+ parser.c
+
+libisccfg_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS)
+
+libisccfg_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS)
+
+libisccfg_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: 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/isccfg/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/isccfg/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}; \
+ }
+
+libisccfg.la: $(libisccfg_la_OBJECTS) $(libisccfg_la_DEPENDENCIES) $(EXTRA_libisccfg_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libisccfg_la_LINK) -rpath $(libdir) $(libisccfg_la_OBJECTS) $(libisccfg_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-aclconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-dnsconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-duration.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-kaspconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-namedconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libisccfg_la-parser.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 $@ $<
+
+libisccfg_la-aclconf.lo: aclconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-aclconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-aclconf.Tpo -c -o libisccfg_la-aclconf.lo `test -f 'aclconf.c' || echo '$(srcdir)/'`aclconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-aclconf.Tpo $(DEPDIR)/libisccfg_la-aclconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='aclconf.c' object='libisccfg_la-aclconf.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-aclconf.lo `test -f 'aclconf.c' || echo '$(srcdir)/'`aclconf.c
+
+libisccfg_la-dnsconf.lo: dnsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-dnsconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-dnsconf.Tpo -c -o libisccfg_la-dnsconf.lo `test -f 'dnsconf.c' || echo '$(srcdir)/'`dnsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-dnsconf.Tpo $(DEPDIR)/libisccfg_la-dnsconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnsconf.c' object='libisccfg_la-dnsconf.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-dnsconf.lo `test -f 'dnsconf.c' || echo '$(srcdir)/'`dnsconf.c
+
+libisccfg_la-duration.lo: duration.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-duration.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-duration.Tpo -c -o libisccfg_la-duration.lo `test -f 'duration.c' || echo '$(srcdir)/'`duration.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-duration.Tpo $(DEPDIR)/libisccfg_la-duration.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='duration.c' object='libisccfg_la-duration.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-duration.lo `test -f 'duration.c' || echo '$(srcdir)/'`duration.c
+
+libisccfg_la-kaspconf.lo: kaspconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-kaspconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-kaspconf.Tpo -c -o libisccfg_la-kaspconf.lo `test -f 'kaspconf.c' || echo '$(srcdir)/'`kaspconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-kaspconf.Tpo $(DEPDIR)/libisccfg_la-kaspconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='kaspconf.c' object='libisccfg_la-kaspconf.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-kaspconf.lo `test -f 'kaspconf.c' || echo '$(srcdir)/'`kaspconf.c
+
+libisccfg_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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-log.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-log.Tpo -c -o libisccfg_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-log.Tpo $(DEPDIR)/libisccfg_la-log.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libisccfg_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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+libisccfg_la-namedconf.lo: namedconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-namedconf.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-namedconf.Tpo -c -o libisccfg_la-namedconf.lo `test -f 'namedconf.c' || echo '$(srcdir)/'`namedconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-namedconf.Tpo $(DEPDIR)/libisccfg_la-namedconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='namedconf.c' object='libisccfg_la-namedconf.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-namedconf.lo `test -f 'namedconf.c' || echo '$(srcdir)/'`namedconf.c
+
+libisccfg_la-parser.lo: parser.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libisccfg_la-parser.lo -MD -MP -MF $(DEPDIR)/libisccfg_la-parser.Tpo -c -o libisccfg_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libisccfg_la-parser.Tpo $(DEPDIR)/libisccfg_la-parser.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='parser.c' object='libisccfg_la-parser.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) $(libisccfg_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libisccfg_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libisccfg_laHEADERS: $(libisccfg_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libisccfg_la_HEADERS)'; test -n "$(libisccfg_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libisccfg_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libisccfg_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)$(libisccfg_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libisccfg_ladir)" || exit $$?; \
+ done
+
+uninstall-libisccfg_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libisccfg_la_HEADERS)'; test -n "$(libisccfg_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libisccfg_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libisccfg_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libisccfg_la-aclconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-dnsconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-duration.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-kaspconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-namedconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-parser.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-libisccfg_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)/libisccfg_la-aclconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-dnsconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-duration.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-kaspconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-log.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-namedconf.Plo
+ -rm -f ./$(DEPDIR)/libisccfg_la-parser.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-libLTLIBRARIES uninstall-libisccfg_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libisccfg_laHEADERS install-man 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-libLTLIBRARIES uninstall-libisccfg_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/isccfg/aclconf.c b/lib/isccfg/aclconf.c
new file mode 100644
index 0000000..1e3566a
--- /dev/null
+++ b/lib/isccfg/aclconf.c
@@ -0,0 +1,1010 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/string.h> /* Required for HP/UX (and others?) */
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/fixedname.h>
+#include <dns/iptable.h>
+#include <dns/log.h>
+
+#include <isccfg/aclconf.h>
+#include <isccfg/namedconf.h>
+
+#define LOOP_MAGIC ISC_MAGIC('L', 'O', 'O', 'P')
+
+#if defined(HAVE_GEOIP2)
+static const char *geoip_dbnames[] = {
+ "country", "city", "asnum", "isp", "domain", NULL,
+};
+#endif /* if defined(HAVE_GEOIP2) */
+
+isc_result_t
+cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret) {
+ cfg_aclconfctx_t *actx;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ actx = isc_mem_get(mctx, sizeof(*actx));
+
+ isc_refcount_init(&actx->references, 1);
+
+ actx->mctx = NULL;
+ isc_mem_attach(mctx, &actx->mctx);
+ ISC_LIST_INIT(actx->named_acl_cache);
+
+#if defined(HAVE_GEOIP2)
+ actx->geoip = NULL;
+#endif /* if defined(HAVE_GEOIP2) */
+
+ *ret = actx;
+ return (ISC_R_SUCCESS);
+}
+
+void
+cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+void
+cfg_aclconfctx_detach(cfg_aclconfctx_t **actxp) {
+ REQUIRE(actxp != NULL && *actxp != NULL);
+ cfg_aclconfctx_t *actx = *actxp;
+ *actxp = NULL;
+
+ if (isc_refcount_decrement(&actx->references) == 1) {
+ dns_acl_t *dacl, *next;
+ isc_refcount_destroy(&actx->references);
+ for (dacl = ISC_LIST_HEAD(actx->named_acl_cache); dacl != NULL;
+ dacl = next)
+ {
+ next = ISC_LIST_NEXT(dacl, nextincache);
+ ISC_LIST_UNLINK(actx->named_acl_cache, dacl,
+ nextincache);
+ dns_acl_detach(&dacl);
+ }
+ isc_mem_putanddetach(&actx->mctx, actx, sizeof(*actx));
+ }
+}
+
+/*
+ * Find the definition of the named acl whose name is "name".
+ */
+static isc_result_t
+get_acl_def(const cfg_obj_t *cctx, const char *name, const cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_obj_t *acls = NULL;
+ const cfg_listelt_t *elt;
+
+ result = cfg_map_get(cctx, "acl", &acls);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ for (elt = cfg_list_first(acls); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *acl = cfg_listelt_value(elt);
+ const char *aclname =
+ cfg_obj_asstring(cfg_tuple_get(acl, "name"));
+ if (strcasecmp(aclname, name) == 0) {
+ if (ret != NULL) {
+ *ret = cfg_tuple_get(acl, "value");
+ }
+ return (ISC_R_SUCCESS);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+}
+
+static isc_result_t
+convert_named_acl(const cfg_obj_t *nameobj, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target) {
+ isc_result_t result;
+ const cfg_obj_t *cacl = NULL;
+ dns_acl_t *dacl;
+ dns_acl_t loop;
+ const char *aclname = cfg_obj_asstring(nameobj);
+
+ /* Look for an already-converted version. */
+ for (dacl = ISC_LIST_HEAD(ctx->named_acl_cache); dacl != NULL;
+ dacl = ISC_LIST_NEXT(dacl, nextincache))
+ {
+ if (strcasecmp(aclname, dacl->name) == 0) {
+ if (ISC_MAGIC_VALID(dacl, LOOP_MAGIC)) {
+ cfg_obj_log(nameobj, lctx, ISC_LOG_ERROR,
+ "acl loop detected: %s", aclname);
+ return (ISC_R_FAILURE);
+ }
+ dns_acl_attach(dacl, target);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ /* Not yet converted. Convert now. */
+ result = get_acl_def(cctx, aclname, &cacl);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(nameobj, lctx, ISC_LOG_WARNING,
+ "undefined ACL '%s'", aclname);
+ return (result);
+ }
+ /*
+ * Add a loop detection element.
+ */
+ memset(&loop, 0, sizeof(loop));
+ ISC_LINK_INIT(&loop, nextincache);
+ DE_CONST(aclname, loop.name);
+ loop.magic = LOOP_MAGIC;
+ ISC_LIST_APPEND(ctx->named_acl_cache, &loop, nextincache);
+ result = cfg_acl_fromconfig(cacl, cctx, lctx, ctx, mctx, nest_level,
+ &dacl);
+ ISC_LIST_UNLINK(ctx->named_acl_cache, &loop, nextincache);
+ loop.magic = 0;
+ loop.name = NULL;
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dacl->name = isc_mem_strdup(dacl->mctx, aclname);
+ ISC_LIST_APPEND(ctx->named_acl_cache, dacl, nextincache);
+ dns_acl_attach(dacl, target);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+convert_keyname(const cfg_obj_t *keyobj, isc_log_t *lctx, isc_mem_t *mctx,
+ dns_name_t *dnsname) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ dns_fixedname_t fixname;
+ unsigned int keylen;
+ const char *txtname = cfg_obj_asstring(keyobj);
+
+ keylen = strlen(txtname);
+ isc_buffer_constinit(&buf, txtname, keylen);
+ isc_buffer_add(&buf, keylen);
+ dns_fixedname_init(&fixname);
+ result = dns_name_fromtext(dns_fixedname_name(&fixname), &buf,
+ dns_rootname, 0, NULL);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(keyobj, lctx, ISC_LOG_WARNING,
+ "key name '%s' is not a valid domain name",
+ txtname);
+ return (result);
+ }
+ dns_name_dup(dns_fixedname_name(&fixname), mctx, dnsname);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Recursively pre-parse an ACL definition to find the total number
+ * of non-IP-prefix elements (localhost, localnets, key) in all nested
+ * ACLs, so that the parent will have enough space allocated for the
+ * elements table after all the nested ACLs have been merged in to the
+ * parent.
+ */
+static isc_result_t
+count_acl_elements(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ uint32_t *count, bool *has_negative) {
+ const cfg_listelt_t *elt;
+ isc_result_t result;
+ uint32_t n = 0;
+
+ REQUIRE(count != NULL);
+
+ if (has_negative != NULL) {
+ *has_negative = false;
+ }
+
+ for (elt = cfg_list_first(caml); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *ce = cfg_listelt_value(elt);
+
+ /* might be a negated element, in which case get the value. */
+ if (cfg_obj_istuple(ce)) {
+ const cfg_obj_t *negated = cfg_tuple_get(ce, "negated");
+ if (!cfg_obj_isvoid(negated)) {
+ ce = negated;
+ if (has_negative != NULL) {
+ *has_negative = true;
+ }
+ }
+ }
+
+ if (cfg_obj_istype(ce, &cfg_type_keyref)) {
+ n++;
+ } else if (cfg_obj_islist(ce)) {
+ bool negative;
+ uint32_t sub;
+ result = count_acl_elements(ce, cctx, lctx, ctx, mctx,
+ &sub, &negative);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ n += sub;
+ if (negative) {
+ n++;
+ }
+#if defined(HAVE_GEOIP2)
+ } else if (cfg_obj_istuple(ce) &&
+ cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
+ {
+ n++;
+#endif /* HAVE_GEOIP2 */
+ } else if (cfg_obj_isstring(ce)) {
+ const char *name = cfg_obj_asstring(ce);
+ if (strcasecmp(name, "localhost") == 0 ||
+ strcasecmp(name, "localnets") == 0 ||
+ strcasecmp(name, "none") == 0)
+ {
+ n++;
+ } else if (strcasecmp(name, "any") != 0) {
+ dns_acl_t *inneracl = NULL;
+ /*
+ * Convert any named acls we reference now if
+ * they have not already been converted.
+ */
+ result = convert_named_acl(ce, cctx, lctx, ctx,
+ mctx, 0, &inneracl);
+ if (result == ISC_R_SUCCESS) {
+ if (inneracl->has_negatives) {
+ n++;
+ } else {
+ n += inneracl->length;
+ }
+ dns_acl_detach(&inneracl);
+ } else {
+ return (result);
+ }
+ }
+ }
+ }
+
+ *count = n;
+ return (ISC_R_SUCCESS);
+}
+
+#if defined(HAVE_GEOIP2)
+static dns_geoip_subtype_t
+get_subtype(const cfg_obj_t *obj, isc_log_t *lctx, dns_geoip_subtype_t subtype,
+ const char *dbname) {
+ if (dbname == NULL) {
+ return (subtype);
+ }
+
+ switch (subtype) {
+ case dns_geoip_countrycode:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_countrycode);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_code);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "country search: ignored");
+ return (subtype);
+ case dns_geoip_countryname:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_countryname);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_name);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "country search: ignored");
+ return (subtype);
+ case dns_geoip_continentcode:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_continentcode);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_continentcode);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "continent search: ignored");
+ return (subtype);
+ case dns_geoip_continent:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_continent);
+ } else if (strcasecmp(dbname, "country") == 0) {
+ return (dns_geoip_country_continent);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "continent search: ignored");
+ return (subtype);
+ case dns_geoip_region:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_region);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "region/subdivision search: ignored");
+ return (subtype);
+ case dns_geoip_regionname:
+ if (strcasecmp(dbname, "city") == 0) {
+ return (dns_geoip_city_regionname);
+ }
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "invalid database specified for "
+ "region/subdivision search: ignored");
+ return (subtype);
+
+ /*
+ * Log a warning if the wrong database was specified
+ * on an unambiguous query
+ */
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ case dns_geoip_city_timezonecode:
+ if (strcasecmp(dbname, "city") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'city'-only search type: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_isp_name:
+ if (strcasecmp(dbname, "isp") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'isp' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_org_name:
+ if (strcasecmp(dbname, "org") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'org' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_as_asnum:
+ if (strcasecmp(dbname, "asnum") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "an 'asnum' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_domain_name:
+ if (strcasecmp(dbname, "domain") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'domain' search: ignoring");
+ }
+ return (subtype);
+ case dns_geoip_netspeed_id:
+ if (strcasecmp(dbname, "netspeed") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_WARNING,
+ "invalid database specified for "
+ "a 'netspeed' search: ignoring");
+ }
+ return (subtype);
+ default:
+ UNREACHABLE();
+ }
+}
+
+static bool
+geoip_can_answer(dns_aclelement_t *elt, cfg_aclconfctx_t *ctx) {
+ if (ctx->geoip == NULL) {
+ return (true);
+ }
+
+ switch (elt->geoip_elem.subtype) {
+ case dns_geoip_countrycode:
+ case dns_geoip_countryname:
+ case dns_geoip_continentcode:
+ case dns_geoip_continent:
+ if (ctx->geoip->country != NULL || ctx->geoip->city != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_country_code:
+ case dns_geoip_country_name:
+ case dns_geoip_country_continentcode:
+ case dns_geoip_country_continent:
+ if (ctx->geoip->country != NULL) {
+ return (true);
+ }
+ /* city db can answer these too, so: */
+ FALLTHROUGH;
+ case dns_geoip_region:
+ case dns_geoip_regionname:
+ case dns_geoip_city_countrycode:
+ case dns_geoip_city_countryname:
+ case dns_geoip_city_region:
+ case dns_geoip_city_regionname:
+ case dns_geoip_city_name:
+ case dns_geoip_city_postalcode:
+ case dns_geoip_city_metrocode:
+ case dns_geoip_city_areacode:
+ case dns_geoip_city_continentcode:
+ case dns_geoip_city_continent:
+ case dns_geoip_city_timezonecode:
+ if (ctx->geoip->city != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_isp_name:
+ if (ctx->geoip->isp != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_as_asnum:
+ case dns_geoip_org_name:
+ if (ctx->geoip->as != NULL) {
+ return (true);
+ }
+ break;
+ case dns_geoip_domain_name:
+ if (ctx->geoip->domain != NULL) {
+ return (true);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return (false);
+}
+
+static isc_result_t
+parse_geoip_element(const cfg_obj_t *obj, isc_log_t *lctx,
+ cfg_aclconfctx_t *ctx, dns_aclelement_t *dep) {
+ const cfg_obj_t *ge;
+ const char *dbname = NULL;
+ const char *stype = NULL, *search = NULL;
+ dns_geoip_subtype_t subtype;
+ dns_aclelement_t de;
+ size_t len;
+
+ REQUIRE(dep != NULL);
+
+ de = *dep;
+
+ ge = cfg_tuple_get(obj, "db");
+ if (!cfg_obj_isvoid(ge)) {
+ int i;
+
+ dbname = cfg_obj_asstring(ge);
+
+ for (i = 0; geoip_dbnames[i] != NULL; i++) {
+ if (strcasecmp(dbname, geoip_dbnames[i]) == 0) {
+ break;
+ }
+ }
+ if (geoip_dbnames[i] == NULL) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "database '%s' is not defined for GeoIP2",
+ dbname);
+ return (ISC_R_UNEXPECTED);
+ }
+ }
+
+ stype = cfg_obj_asstring(cfg_tuple_get(obj, "subtype"));
+ search = cfg_obj_asstring(cfg_tuple_get(obj, "search"));
+ len = strlen(search);
+
+ if (len == 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "zero-length geoip search field");
+ return (ISC_R_FAILURE);
+ }
+
+ if (strcasecmp(stype, "country") == 0 && len == 2) {
+ /* Two-letter country code */
+ subtype = dns_geoip_countrycode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "country") == 0 && len == 3) {
+ /* Three-letter country code */
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "three-letter country codes are unavailable "
+ "in GeoIP2 databases");
+ return (ISC_R_FAILURE);
+ } else if (strcasecmp(stype, "country") == 0) {
+ /* Country name */
+ subtype = dns_geoip_countryname;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "continent") == 0 && len == 2) {
+ /* Two-letter continent code */
+ subtype = dns_geoip_continentcode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "continent") == 0) {
+ subtype = dns_geoip_continent;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if ((strcasecmp(stype, "region") == 0 ||
+ strcasecmp(stype, "subdivision") == 0) &&
+ len == 2)
+ {
+ /* Two-letter region code */
+ subtype = dns_geoip_region;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "region") == 0 ||
+ strcasecmp(stype, "subdivision") == 0)
+ {
+ /* Region name */
+ subtype = dns_geoip_regionname;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "city") == 0) {
+ /* City name */
+ subtype = dns_geoip_city_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "postal") == 0 ||
+ strcasecmp(stype, "postalcode") == 0)
+ {
+ if (len < 7) {
+ subtype = dns_geoip_city_postalcode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "geoiop postal code (%s) too long", search);
+ return (ISC_R_FAILURE);
+ }
+ } else if (strcasecmp(stype, "metro") == 0 ||
+ strcasecmp(stype, "metrocode") == 0)
+ {
+ subtype = dns_geoip_city_metrocode;
+ de.geoip_elem.as_int = atoi(search);
+ } else if (strcasecmp(stype, "tz") == 0 ||
+ strcasecmp(stype, "timezone") == 0)
+ {
+ subtype = dns_geoip_city_timezonecode;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "isp") == 0) {
+ subtype = dns_geoip_isp_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "asnum") == 0) {
+ subtype = dns_geoip_as_asnum;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "org") == 0) {
+ subtype = dns_geoip_org_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else if (strcasecmp(stype, "domain") == 0) {
+ subtype = dns_geoip_domain_name;
+ strlcpy(de.geoip_elem.as_string, search,
+ sizeof(de.geoip_elem.as_string));
+ } else {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "type '%s' is unavailable "
+ "in GeoIP2 databases",
+ stype);
+ return (ISC_R_FAILURE);
+ }
+
+ de.geoip_elem.subtype = get_subtype(obj, lctx, subtype, dbname);
+
+ if (!geoip_can_answer(&de, ctx)) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "no GeoIP2 database installed which can answer "
+ "queries of type '%s'",
+ stype);
+ return (ISC_R_FAILURE);
+ }
+
+ *dep = de;
+
+ return (ISC_R_SUCCESS);
+}
+#endif /* HAVE_GEOIP2 */
+
+isc_result_t
+cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target) {
+ return (cfg_acl_fromconfig2(caml, cctx, lctx, ctx, mctx, nest_level, 0,
+ target));
+}
+
+isc_result_t
+cfg_acl_fromconfig2(const cfg_obj_t *acl_data, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, uint16_t family,
+ dns_acl_t **target) {
+ isc_result_t result;
+ dns_acl_t *dacl = NULL, *inneracl = NULL;
+ dns_aclelement_t *de;
+ const cfg_listelt_t *elt;
+ dns_iptable_t *iptab;
+ int new_nest_level = 0;
+ bool setpos;
+ const cfg_obj_t *caml = NULL;
+ const cfg_obj_t *obj_acl_tuple = NULL;
+ const cfg_obj_t *obj_port = NULL, *obj_transport = NULL;
+ bool is_tuple = false;
+
+ if (nest_level != 0) {
+ new_nest_level = nest_level - 1;
+ }
+
+ REQUIRE(ctx != NULL);
+ REQUIRE(target != NULL);
+ REQUIRE(*target == NULL || DNS_ACL_VALID(*target));
+
+ REQUIRE(acl_data != NULL);
+ if (cfg_obj_islist(acl_data)) {
+ caml = acl_data;
+ } else {
+ INSIST(cfg_obj_istuple(acl_data));
+ caml = cfg_tuple_get(acl_data, "aml");
+ INSIST(caml != NULL);
+ obj_acl_tuple = cfg_tuple_get(acl_data, "port-transport");
+ INSIST(obj_acl_tuple != NULL);
+ obj_port = cfg_tuple_get(obj_acl_tuple, "port");
+ obj_transport = cfg_tuple_get(obj_acl_tuple, "transport");
+ is_tuple = true;
+ }
+
+ if (*target != NULL) {
+ /*
+ * If target already points to an ACL, then we're being
+ * called recursively to configure a nested ACL. The
+ * nested ACL's contents should just be absorbed into its
+ * parent ACL.
+ */
+ dns_acl_attach(*target, &dacl);
+ dns_acl_detach(target);
+ } else {
+ /*
+ * Need to allocate a new ACL structure. Count the items
+ * in the ACL definition that will require space in the
+ * elements table. (Note that if nest_level is nonzero,
+ * *everything* goes in the elements table.)
+ */
+ uint32_t nelem;
+
+ if (nest_level == 0) {
+ result = count_acl_elements(caml, cctx, lctx, ctx, mctx,
+ &nelem, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ nelem = cfg_list_length(caml, false);
+ }
+
+ result = dns_acl_create(mctx, nelem, &dacl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ if (is_tuple) {
+ uint16_t port = 0;
+ uint32_t transports = 0;
+ bool encrypted = false;
+
+ if (obj_port != NULL && cfg_obj_isuint32(obj_port)) {
+ port = (uint16_t)cfg_obj_asuint32(obj_port);
+ }
+
+ if (obj_transport != NULL && cfg_obj_isstring(obj_transport)) {
+ if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "udp") == 0)
+ {
+ transports = isc_nm_udpsocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "tcp") == 0)
+ {
+ transports = isc_nm_tcpdnssocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "udp-tcp") == 0)
+ {
+ /* Good ol' DNS over port 53 */
+ transports = isc_nm_tcpdnssocket |
+ isc_nm_udpsocket;
+ encrypted = false;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "tls") == 0)
+ {
+ transports = isc_nm_tlsdnssocket;
+ encrypted = true;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "http") == 0)
+ {
+ transports = isc_nm_httpsocket;
+ encrypted = true;
+ } else if (strcasecmp(cfg_obj_asstring(obj_transport),
+ "http-plain") == 0)
+ {
+ transports = isc_nm_httpsocket;
+ encrypted = false;
+ } else {
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ if (port != 0 || transports != 0) {
+ dns_acl_add_port_transports(dacl, port, transports,
+ encrypted, false);
+ }
+ }
+
+ de = dacl->elements;
+ for (elt = cfg_list_first(caml); elt != NULL; elt = cfg_list_next(elt))
+ {
+ const cfg_obj_t *ce = cfg_listelt_value(elt);
+ bool neg = false;
+
+ INSIST(dacl->length <= dacl->alloc);
+
+ if (cfg_obj_istuple(ce)) {
+ /* Might be a negated element */
+ const cfg_obj_t *negated = cfg_tuple_get(ce, "negated");
+ if (!cfg_obj_isvoid(negated)) {
+ neg = true;
+ dacl->has_negatives = true;
+ ce = negated;
+ }
+ }
+
+ /*
+ * If nest_level is nonzero, then every element is
+ * to be stored as a separate, nested ACL rather than
+ * merged into the main iptable.
+ */
+ iptab = dacl->iptable;
+
+ if (nest_level != 0) {
+ result = dns_acl_create(mctx,
+ cfg_list_length(ce, false),
+ &de->nestedacl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ iptab = de->nestedacl->iptable;
+ }
+
+ if (cfg_obj_isnetprefix(ce)) {
+ /* Network prefix */
+ isc_netaddr_t addr;
+ unsigned int bitlen;
+
+ cfg_obj_asnetprefix(ce, &addr, &bitlen);
+ if (family != 0 && family != addr.family) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&addr, buf, sizeof(buf));
+ cfg_obj_log(ce, lctx, ISC_LOG_WARNING,
+ "'%s': incorrect address family; "
+ "ignoring",
+ buf);
+ if (nest_level != 0) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ continue;
+ }
+ result = isc_netaddr_prefixok(&addr, bitlen);
+ if (result != ISC_R_SUCCESS) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&addr, buf, sizeof(buf));
+ cfg_obj_log(ce, lctx, ISC_LOG_ERROR,
+ "'%s/%u': address/prefix length "
+ "mismatch",
+ buf, bitlen);
+ goto cleanup;
+ }
+
+ /*
+ * If nesting ACLs (nest_level != 0), we negate
+ * the nestedacl element, not the iptable entry.
+ */
+ setpos = (nest_level != 0 || !neg);
+ result = dns_iptable_addprefix(iptab, &addr, bitlen,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (nest_level > 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ } else {
+ continue;
+ }
+ } else if (cfg_obj_islist(ce)) {
+ /*
+ * If we're nesting ACLs, put the nested
+ * ACL onto the elements list; otherwise
+ * merge it into *this* ACL. We nest ACLs
+ * in two cases: 1) sortlist, 2) if the
+ * nested ACL contains negated members.
+ */
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ result = cfg_acl_fromconfig(ce, cctx, lctx, ctx, mctx,
+ new_nest_level, &inneracl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ nested_acl:
+ if (nest_level > 0 || inneracl->has_negatives) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ if (de->nestedacl != NULL) {
+ dns_acl_detach(&de->nestedacl);
+ }
+ /*
+ * Merge the port-transports entries from the
+ * nested ACL into its parent.
+ */
+ dns_acl_merge_ports_transports(dacl, inneracl,
+ !neg);
+ dns_acl_attach(inneracl, &de->nestedacl);
+ dns_acl_detach(&inneracl);
+ /* Fall through. */
+ } else {
+ INSIST(dacl->length + inneracl->length <=
+ dacl->alloc);
+ dns_acl_merge(dacl, inneracl, !neg);
+ de += inneracl->length; /* elements added */
+ dns_acl_detach(&inneracl);
+ INSIST(dacl->length <= dacl->alloc);
+ continue;
+ }
+ } else if (cfg_obj_istype(ce, &cfg_type_keyref)) {
+ /* Key name. */
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_keyname;
+ de->negative = neg;
+ dns_name_init(&de->keyname, NULL);
+ result = convert_keyname(ce, lctx, mctx, &de->keyname);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+#if defined(HAVE_GEOIP2)
+ } else if (cfg_obj_istuple(ce) &&
+ cfg_obj_isvoid(cfg_tuple_get(ce, "negated")))
+ {
+ INSIST(dacl->length < dacl->alloc);
+ result = parse_geoip_element(ce, lctx, ctx, de);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ de->type = dns_aclelementtype_geoip;
+ de->negative = neg;
+#endif /* HAVE_GEOIP2 */
+ } else if (cfg_obj_isstring(ce)) {
+ /* ACL name. */
+ const char *name = cfg_obj_asstring(ce);
+ if (strcasecmp(name, "any") == 0) {
+ /* Iptable entry with zero bit length. */
+ setpos = (nest_level != 0 || !neg);
+ result = dns_iptable_addprefix(iptab, NULL, 0,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (nest_level != 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = neg;
+ } else {
+ continue;
+ }
+ } else if (strcasecmp(name, "none") == 0) {
+ /* none == !any */
+ /*
+ * We don't unconditional set
+ * dacl->has_negatives and
+ * de->negative to true so we can handle
+ * "!none;".
+ */
+ setpos = (nest_level != 0 || neg);
+ result = dns_iptable_addprefix(iptab, NULL, 0,
+ setpos);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (!neg) {
+ dacl->has_negatives = !neg;
+ }
+
+ if (nest_level != 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_nestedacl;
+ de->negative = !neg;
+ } else {
+ continue;
+ }
+ } else if (strcasecmp(name, "localhost") == 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_localhost;
+ de->negative = neg;
+ } else if (strcasecmp(name, "localnets") == 0) {
+ INSIST(dacl->length < dacl->alloc);
+ de->type = dns_aclelementtype_localnets;
+ de->negative = neg;
+ } else {
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ /*
+ * This call should just find the cached
+ * of the named acl.
+ */
+ result = convert_named_acl(ce, cctx, lctx, ctx,
+ mctx, new_nest_level,
+ &inneracl);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ goto nested_acl;
+ }
+ } else {
+ cfg_obj_log(ce, lctx, ISC_LOG_WARNING,
+ "address match list contains "
+ "unsupported element type");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+
+ /*
+ * This should only be reached for localhost, localnets
+ * and keyname elements, and nested ACLs if nest_level is
+ * nonzero (i.e., in sortlists).
+ */
+ if (de->nestedacl != NULL &&
+ de->type != dns_aclelementtype_nestedacl)
+ {
+ dns_acl_detach(&de->nestedacl);
+ }
+
+ dns_acl_node_count(dacl)++;
+ de->node_num = dns_acl_node_count(dacl);
+
+ dacl->length++;
+ de++;
+ INSIST(dacl->length <= dacl->alloc);
+ }
+
+ dns_acl_attach(dacl, target);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (inneracl != NULL) {
+ dns_acl_detach(&inneracl);
+ }
+ dns_acl_detach(&dacl);
+ return (result);
+}
diff --git a/lib/isccfg/dnsconf.c b/lib/isccfg/dnsconf.c
new file mode 100644
index 0000000..ccd7232
--- /dev/null
+++ b/lib/isccfg/dnsconf.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+
+/*%
+ * A trusted key, as used in the "trusted-keys" statement.
+ */
+static cfg_tuplefielddef_t trustedkey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "flags", &cfg_type_uint32, 0 },
+ { "protocol", &cfg_type_uint32, 0 },
+ { "algorithm", &cfg_type_uint32, 0 },
+ { "key", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_trustedkey = { "trustedkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, trustedkey_fields };
+
+static cfg_type_t cfg_type_trustedkeys = { "trusted-keys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_trustedkey };
+
+/*%
+ * Clauses that can be found within the top level of the dns.conf
+ * file only.
+ */
+static cfg_clausedef_t dnsconf_clauses[] = {
+ { "trusted-keys", &cfg_type_trustedkeys, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+/*% The top-level dns.conf syntax. */
+
+static cfg_clausedef_t *dnsconf_clausesets[] = { dnsconf_clauses, NULL };
+
+cfg_type_t cfg_type_dnsconf = { "dnsconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, dnsconf_clausesets };
diff --git a/lib/isccfg/duration.c b/lib/isccfg/duration.c
new file mode 100644
index 0000000..9ed9d6f
--- /dev/null
+++ b/lib/isccfg/duration.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdint.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>
+
+#include <isccfg/duration.h>
+
+/*
+ * isccfg_duration_fromtext initially taken from OpenDNSSEC code base.
+ * Modified to fit the BIND 9 code.
+ */
+isc_result_t
+isccfg_duration_fromtext(isc_textregion_t *source,
+ isccfg_duration_t *duration) {
+ char buf[CFG_DURATION_MAXLEN] = { 0 };
+ char *P, *X, *T, *W, *str;
+ bool not_weeks = false;
+ int i;
+ long long int lli;
+
+ /*
+ * Copy the buffer as it may not be NULL terminated.
+ */
+ if (source->length > sizeof(buf) - 1) {
+ return (ISC_R_BADNUMBER);
+ }
+ /* Copy source->length bytes and NULL terminate. */
+ snprintf(buf, sizeof(buf), "%.*s", (int)source->length, source->base);
+ str = buf;
+
+ /* Clear out duration. */
+ for (i = 0; i < 7; i++) {
+ duration->parts[i] = 0;
+ }
+ duration->iso8601 = false;
+ duration->unlimited = false;
+
+ /* Every duration starts with 'P' */
+ if (toupper((unsigned char)str[0]) != 'P') {
+ return (ISC_R_BADNUMBER);
+ }
+ P = str;
+
+ /* Record the time indicator. */
+ T = strpbrk(str, "Tt");
+
+ /* Record years. */
+ X = strpbrk(str, "Yy");
+ if (X != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[0] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record months. */
+ X = strpbrk(str, "Mm");
+
+ /*
+ * M could be months or minutes. This is months if there is no time
+ * part, or this M indicator is before the time indicator.
+ */
+ if (X != NULL && (T == NULL || (size_t)(X - P) < (size_t)(T - P))) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[1] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record days. */
+ X = strpbrk(str, "Dd");
+ if (X != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[3] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Time part? */
+ if (T != NULL) {
+ str = T;
+ not_weeks = true;
+ }
+
+ /* Record hours. */
+ X = strpbrk(str, "Hh");
+ if (X != NULL && T != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[4] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record minutes. */
+ X = strpbrk(str, "Mm");
+
+ /*
+ * M could be months or minutes. This is minutes if there is a time
+ * part and the M indicator is behind the time indicator.
+ */
+ if (X != NULL && T != NULL && (size_t)(X - P) > (size_t)(T - P)) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[5] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Record seconds. */
+ X = strpbrk(str, "Ss");
+ if (X != NULL && T != NULL) {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[6] = (uint32_t)lli;
+ str = X;
+ not_weeks = true;
+ }
+
+ /* Or is the duration configured in weeks? */
+ W = strpbrk(buf, "Ww");
+ if (W != NULL) {
+ if (not_weeks) {
+ /* Mix of weeks and other indicators is not allowed */
+ return (ISC_R_BADNUMBER);
+ } else {
+ errno = 0;
+ lli = strtoll(str + 1, NULL, 10);
+ if (errno != 0 || lli < 0 || lli > UINT32_MAX) {
+ return (ISC_R_BADNUMBER);
+ }
+ duration->parts[2] = (uint32_t)lli;
+ str = W;
+ }
+ }
+
+ /* Deal with trailing garbage. */
+ if (str[1] != '\0') {
+ return (ISC_R_BADNUMBER);
+ }
+
+ duration->iso8601 = true;
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+isccfg_parse_duration(isc_textregion_t *source, isccfg_duration_t *duration) {
+ isc_result_t result;
+
+ REQUIRE(duration != NULL);
+
+ duration->unlimited = false;
+ result = isccfg_duration_fromtext(source, duration);
+ if (result == ISC_R_BADNUMBER) {
+ /* Fallback to dns_ttl_fromtext. */
+ uint32_t ttl;
+ result = dns_ttl_fromtext(source, &ttl);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * With dns_ttl_fromtext() the information on optional
+ * units is lost, and is treated as seconds from now on.
+ */
+ duration->iso8601 = false;
+ duration->parts[6] = ttl;
+ }
+ }
+
+ return (result);
+}
+
+uint32_t
+isccfg_duration_toseconds(const isccfg_duration_t *duration) {
+ uint64_t seconds = 0;
+
+ REQUIRE(duration != NULL);
+
+ seconds += (uint64_t)duration->parts[6]; /* Seconds */
+ seconds += (uint64_t)duration->parts[5] * 60; /* Minutes */
+ seconds += (uint64_t)duration->parts[4] * 3600; /* Hours */
+ seconds += (uint64_t)duration->parts[3] * 86400; /* Days */
+ seconds += (uint64_t)duration->parts[2] * 86400 * 7; /* Weeks */
+ /*
+ * The below additions are not entirely correct
+ * because days may vary per month and per year.
+ */
+ seconds += (uint64_t)duration->parts[1] * 86400 * 31; /* Months */
+ seconds += (uint64_t)duration->parts[0] * 86400 * 365; /* Years */
+
+ return (seconds > UINT32_MAX ? UINT32_MAX : (uint32_t)seconds);
+}
diff --git a/lib/isccfg/include/isccfg/aclconf.h b/lib/isccfg/include/isccfg/aclconf.h
new file mode 100644
index 0000000..eb1b9ab
--- /dev/null
+++ b/lib/isccfg/include/isccfg/aclconf.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
+
+#include <inttypes.h>
+
+#include <isc/lang.h>
+
+#include <dns/geoip.h>
+#include <dns/types.h>
+
+#include <isccfg/cfg.h>
+
+typedef struct cfg_aclconfctx {
+ ISC_LIST(dns_acl_t) named_acl_cache;
+ isc_mem_t *mctx;
+#if defined(HAVE_GEOIP2)
+ dns_geoip_databases_t *geoip;
+#endif /* if defined(HAVE_GEOIP2) */
+ isc_refcount_t references;
+} cfg_aclconfctx_t;
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+cfg_aclconfctx_create(isc_mem_t *mctx, cfg_aclconfctx_t **ret);
+/*
+ * Creates and initializes an ACL configuration context.
+ */
+
+void
+cfg_aclconfctx_detach(cfg_aclconfctx_t **actxp);
+/*
+ * Removes a reference to an ACL configuration context; when references
+ * reaches zero, clears the contents and deallocate the structure.
+ */
+
+void
+cfg_aclconfctx_attach(cfg_aclconfctx_t *src, cfg_aclconfctx_t **dest);
+/*
+ * Attaches a pointer to an existing ACL configuration context.
+ */
+
+isc_result_t
+cfg_acl_fromconfig(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, dns_acl_t **target);
+
+isc_result_t
+cfg_acl_fromconfig2(const cfg_obj_t *caml, const cfg_obj_t *cctx,
+ isc_log_t *lctx, cfg_aclconfctx_t *ctx, isc_mem_t *mctx,
+ unsigned int nest_level, uint16_t family,
+ dns_acl_t **target);
+/*
+ * Construct a new dns_acl_t from configuration data in 'caml' and
+ * 'cctx'. Memory is allocated through 'mctx'.
+ *
+ * Any named ACLs referred to within 'caml' will be be converted
+ * into nested dns_acl_t objects. Multiple references to the same
+ * named ACLs will be converted into shared references to a single
+ * nested dns_acl_t object when the referring objects were created
+ * passing the same ACL configuration context 'ctx'.
+ *
+ * cfg_acl_fromconfig() is a backward-compatible version of
+ * cfg_acl_fromconfig2(), which allows an address family to be
+ * specified. If 'family' is not zero, then only addresses/prefixes
+ * of a matching family (AF_INET or AF_INET6) may be configured.
+ *
+ * On success, attach '*target' to the new dns_acl_t object.
+ *
+ * Require:
+ * 'ctx' to be non NULL.
+ * '*target' to be NULL or a valid dns_acl_t.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/cfg.h b/lib/isccfg/include/isccfg/cfg.h
new file mode 100644
index 0000000..c4f2d86
--- /dev/null
+++ b/lib/isccfg/include/isccfg/cfg.h
@@ -0,0 +1,609 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 isccfg/cfg.h
+ * \brief
+ * This is the new, table-driven, YACC-free configuration file parser.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <time.h>
+
+#include <isc/formatcheck.h>
+#include <isc/lang.h>
+#include <isc/list.h>
+#include <isc/refcount.h>
+#include <isc/types.h>
+
+/***
+ *** Types
+ ***/
+
+/*%
+ * A configuration parser.
+ */
+typedef struct cfg_parser cfg_parser_t;
+
+/*%
+ * A configuration type definition object. There is a single
+ * static cfg_type_t object for each data type supported by
+ * the configuration parser.
+ */
+typedef struct cfg_type cfg_type_t;
+
+/*%
+ * A configuration object. This is the basic building block of the
+ * configuration parse tree. It contains a value (which may be
+ * of one of several types) and information identifying the file
+ * and line number the value came from, for printing error
+ * messages.
+ */
+typedef struct cfg_obj cfg_obj_t;
+
+/*%
+ * A configuration object list element.
+ */
+typedef struct cfg_listelt cfg_listelt_t;
+
+/*%
+ * A callback function to be called when parsing an option
+ * that needs to be interpreted at parsing time, like
+ * "directory".
+ */
+typedef isc_result_t (*cfg_parsecallback_t)(const char *clausename,
+ const cfg_obj_t *obj, void *arg);
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_parser_attach(cfg_parser_t *src, cfg_parser_t **dest);
+/*%<
+ * Reference a parser object.
+ */
+
+isc_result_t
+cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret);
+/*%<
+ * Create a configuration file parser. Any warning and error
+ * messages will be logged to 'lctx'.
+ *
+ * The parser object returned can be used for a single call
+ * to cfg_parse_file() or cfg_parse_buffer(). It must not
+ * be reused for parsing multiple files or buffers.
+ */
+
+void
+cfg_parser_setflags(cfg_parser_t *pctx, unsigned int flags, bool turn_on);
+/*%<
+ * Set parser context flags. The flags are not checked for sensibility.
+ * If 'turn_on' is 'true' the flags will be set, otherwise the flags will
+ * be cleared.
+ *
+ * Requires:
+ *\li "pctx" is not NULL.
+ */
+
+void
+cfg_parser_setcallback(cfg_parser_t *pctx, cfg_parsecallback_t callback,
+ void *arg);
+/*%<
+ * Make the parser call 'callback' whenever it encounters
+ * a configuration clause with the callback attribute,
+ * passing it the clause name, the clause value,
+ * and 'arg' as arguments.
+ *
+ * To restore the default of not invoking callbacks, pass
+ * callback==NULL and arg==NULL.
+ */
+
+isc_result_t
+cfg_parse_file(cfg_parser_t *pctx, const char *file, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer, const char *file,
+ unsigned int line, const cfg_type_t *type, unsigned int flags,
+ cfg_obj_t **ret);
+/*%<
+ * Read a configuration containing data of type 'type'
+ * and make '*ret' point to its parse tree.
+ *
+ * The configuration is read from the file 'filename'
+ * (isc_parse_file()) or the buffer 'buffer'
+ * (isc_parse_buffer()).
+ *
+ * If 'file' is not NULL, it is the name of the file, or a name to use
+ * for the buffer in place of the filename, when logging errors.
+ *
+ * If 'line' is not 0, then it is the beginning line number to report
+ * when logging errors. This is useful when passing text that has been
+ * read from the middle of a file.
+ *
+ * Returns an error if the file or buffer does not parse correctly.
+ *
+ * Requires:
+ *\li "filename" is valid.
+ *\li "mem" is valid.
+ *\li "type" is valid.
+ *\li "cfg" is non-NULL and "*cfg" is NULL.
+ *\li "flags" be one or more of CFG_PCTX_NODEPRECATED or zero.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS - success
+ *\li #ISC_R_NOMEMORY - no memory available
+ *\li #ISC_R_INVALIDFILE - file doesn't exist or is unreadable
+ *\li others - file contains errors
+ */
+
+isc_result_t
+cfg_parser_mapadd(cfg_parser_t *pctx, cfg_obj_t *mapobj, cfg_obj_t *obj,
+ const char *clause);
+/*%<
+ * Add the object 'obj' to the specified clause in mapbody 'mapobj'.
+ * Used for adding new zones.
+ *
+ * Require:
+ * \li 'obj' is a valid cfg_obj_t.
+ * \li 'mapobj' is a valid cfg_obj_t of type map.
+ * \li 'pctx' is a valid cfg_parser_t.
+ */
+
+void
+cfg_parser_reset(cfg_parser_t *pctx);
+/*%<
+ * Reset an existing parser so it can be re-used for a new file or
+ * buffer.
+ */
+
+void
+cfg_parser_destroy(cfg_parser_t **pctxp);
+/*%<
+ * Remove a reference to a configuration parser; destroy it if there are no
+ * more references.
+ */
+
+bool
+cfg_obj_isvoid(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of void type (e.g., an optional
+ * value not specified).
+ */
+
+bool
+cfg_obj_ismap(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a map type.
+ */
+
+bool
+cfg_obj_isfixedpoint(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a fixedpoint type.
+ */
+
+bool
+cfg_obj_ispercentage(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a percentage type.
+ */
+
+isc_result_t
+cfg_map_get(const cfg_obj_t *mapobj, const char *name, const cfg_obj_t **obj);
+/*%<
+ * Extract an element from a configuration object, which
+ * must be of a map type.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ * \li 'name' points to a null-terminated string.
+ * \li 'obj' is non-NULL and '*obj' is NULL.
+ *
+ * Returns:
+ * \li #ISC_R_SUCCESS - success
+ * \li #ISC_R_NOTFOUND - name not found in map
+ */
+
+const cfg_obj_t *
+cfg_map_getname(const cfg_obj_t *mapobj);
+/*%<
+ * Get the name of a named map object, like a server "key" clause.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ *
+ * Returns:
+ * \li A pointer to a configuration object naming the map object,
+ * or NULL if the map object does not have a name.
+ */
+
+unsigned int
+cfg_map_count(const cfg_obj_t *mapobj);
+/*%<
+ * Get the number of elements defined in the symbol table of a map object.
+ *
+ * Requires:
+ * \li 'mapobj' points to a valid configuration object of a map type.
+ *
+ * Returns:
+ * \li The number of elements in the map object.
+ */
+
+bool
+cfg_obj_istuple(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a map type.
+ */
+
+const cfg_obj_t *
+cfg_tuple_get(const cfg_obj_t *tupleobj, const char *name);
+/*%<
+ * Extract an element from a configuration object, which
+ * must be of a tuple type.
+ *
+ * Requires:
+ * \li 'tupleobj' points to a valid configuration object of a tuple type.
+ * \li 'name' points to a null-terminated string naming one of the
+ *\li fields of said tuple type.
+ */
+
+bool
+cfg_obj_isuint32(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of integer type.
+ */
+
+uint32_t
+cfg_obj_asuint32(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of 32-bit integer type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of 32-bit integer type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+bool
+cfg_obj_isuint64(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of integer type.
+ */
+
+uint64_t
+cfg_obj_asuint64(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of 64-bit integer type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of 64-bit integer type.
+ *
+ * Returns:
+ * \li A 64-bit unsigned integer.
+ */
+
+uint32_t
+cfg_obj_asfixedpoint(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of fixed point number.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of fixed point type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+uint32_t
+cfg_obj_aspercentage(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of percentage
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of percentage type.
+ *
+ * Returns:
+ * \li A 32-bit unsigned integer.
+ */
+
+bool
+cfg_obj_isduration(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of duration type.
+ */
+
+uint32_t
+cfg_obj_asduration(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of duration
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of duration type.
+ *
+ * Returns:
+ * \li A duration in seconds.
+ */
+
+bool
+cfg_obj_isstring(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of string type.
+ */
+
+const char *
+cfg_obj_asstring(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of a string type
+ * as a null-terminated string.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a string type.
+ *
+ * Returns:
+ * \li A pointer to a null terminated string.
+ */
+
+bool
+cfg_obj_isboolean(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of a boolean type.
+ */
+
+bool
+cfg_obj_asboolean(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object of a boolean type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a boolean type.
+ *
+ * Returns:
+ * \li A boolean value.
+ */
+
+bool
+cfg_obj_issockaddr(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is a socket address.
+ */
+
+const isc_sockaddr_t *
+cfg_obj_assockaddr(const cfg_obj_t *obj);
+/*%<
+ * Returns the value of a configuration object representing a socket address.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a socket address
+ * type.
+ *
+ * Returns:
+ * \li A pointer to a sockaddr. The sockaddr must be copied by the caller
+ * if necessary.
+ */
+
+bool
+cfg_obj_isnetprefix(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is a network prefix.
+ */
+
+void
+cfg_obj_asnetprefix(const cfg_obj_t *obj, isc_netaddr_t *netaddr,
+ unsigned int *prefixlen);
+/*%<
+ * Gets the value of a configuration object representing a network
+ * prefix. The network address is returned through 'netaddr' and the
+ * prefix length in bits through 'prefixlen'.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of network prefix type.
+ *\li 'netaddr' and 'prefixlen' are non-NULL.
+ */
+
+bool
+cfg_obj_islist(const cfg_obj_t *obj);
+/*%<
+ * Return true iff 'obj' is of list type.
+ */
+
+const cfg_listelt_t *
+cfg_list_first(const cfg_obj_t *obj);
+/*%<
+ * Returns the first list element in a configuration object of a list type.
+ *
+ * Requires:
+ * \li 'obj' points to a valid configuration object of a list type or NULL.
+ *
+ * Returns:
+ * \li A pointer to a cfg_listelt_t representing the first list element,
+ * or NULL if the list is empty or nonexistent.
+ */
+
+const cfg_listelt_t *
+cfg_list_next(const cfg_listelt_t *elt);
+/*%<
+ * Returns the next element of a list of configuration objects.
+ *
+ * Requires:
+ * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or
+ * a previous call to cfg_list_next().
+ *
+ * Returns:
+ * \li A pointer to a cfg_listelt_t representing the next element,
+ * or NULL if there are no more elements.
+ */
+
+unsigned int
+cfg_list_length(const cfg_obj_t *obj, bool recurse);
+/*%<
+ * Returns the length of a list of configure objects. If obj is
+ * not a list, returns 0. If recurse is true, add in the length of
+ * all contained lists.
+ */
+
+cfg_obj_t *
+cfg_listelt_value(const cfg_listelt_t *elt);
+/*%<
+ * Returns the configuration object associated with cfg_listelt_t.
+ *
+ * Requires:
+ * \li 'elt' points to cfg_listelt_t obtained from cfg_list_first() or
+ * cfg_list_next().
+ *
+ * Returns:
+ * \li A non-NULL pointer to a configuration object.
+ */
+
+void
+cfg_print(const cfg_obj_t *obj,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+void
+cfg_printx(const cfg_obj_t *obj, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+
+#define CFG_PRINTER_XKEY 0x1 /* '?' out shared keys. */
+#define CFG_PRINTER_ONELINE 0x2 /* print config as a single line */
+#define CFG_PRINTER_ACTIVEONLY \
+ 0x4 /* print only active configuration \
+ * options, omitting ancient, \
+ * obsolete, nonimplemented, \
+ * and test-only options. */
+
+/*%<
+ * Print the configuration object 'obj' by repeatedly calling the
+ * function 'f', passing 'closure' and a region of text starting
+ * at 'text' and comprising 'textlen' characters.
+ *
+ * If CFG_PRINTER_XKEY the contents of shared keys will be obscured
+ * by replacing them with question marks ('?')
+ */
+
+void
+cfg_print_grammar(const cfg_type_t *type, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+/*%<
+ * Print a summary of the grammar of the configuration type 'type'.
+ */
+
+bool
+cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type);
+/*%<
+ * Return true iff 'obj' is of type 'type'.
+ */
+
+void
+cfg_obj_attach(cfg_obj_t *src, cfg_obj_t **dest);
+/*%<
+ * Reference a configuration object.
+ */
+
+void
+cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **obj);
+/*%<
+ * Delete a reference to a configuration object; destroy the object if
+ * there are no more references.
+ *
+ * Require:
+ * \li '*obj' is a valid cfg_obj_t.
+ * \li 'pctx' is a valid cfg_parser_t.
+ */
+
+void
+cfg_obj_log(const cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt,
+ ...) ISC_FORMAT_PRINTF(4, 5);
+/*%<
+ * Log a message concerning configuration object 'obj' to the logging
+ * channel of 'pctx', at log level 'level'. The message will be prefixed
+ * with the file name(s) and line number where 'obj' was defined.
+ */
+
+const char *
+cfg_obj_file(const cfg_obj_t *obj);
+/*%<
+ * Return the file that defined this object.
+ */
+
+unsigned int
+cfg_obj_line(const cfg_obj_t *obj);
+/*%<
+ * Return the line in file where this object was defined.
+ */
+
+const char *
+cfg_map_firstclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx);
+const char *
+cfg_map_nextclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx);
+
+typedef isc_result_t(pluginlist_cb_t)(const cfg_obj_t *config,
+ const cfg_obj_t *obj,
+ const char *plugin_path,
+ const char *parameters,
+ void *callback_data);
+/*%<
+ * Function prototype for the callback used with cfg_pluginlist_foreach().
+ * Called once for each element of the list passed to cfg_pluginlist_foreach().
+ * If this callback returns anything else than #ISC_R_SUCCESS, no further list
+ * elements will be processed.
+ *
+ * \li 'config' - the 'config' object passed to cfg_pluginlist_foreach()
+ * \li 'obj' - object representing the specific "plugin" stanza to be processed
+ * \li 'plugin_path' - path to the shared object with plugin code
+ * \li 'parameters' - configuration text for the plugin
+ * \li 'callback_data' - the pointer passed to cfg_pluginlist_foreach()
+ */
+
+isc_result_t
+cfg_pluginlist_foreach(const cfg_obj_t *config, const cfg_obj_t *list,
+ isc_log_t *lctx, pluginlist_cb_t *callback,
+ void *callback_data);
+/*%<
+ * For every "plugin" stanza present in 'list' (which in turn is a part of
+ * 'config'), invoke the given 'callback', passing 'callback_data' to it along
+ * with a fixed set of arguments (see the definition of the #pluginlist_cb_t
+ * type). Use logging context 'lctx' for logging error messages. Interrupt
+ * processing if 'callback' returns something else than #ISC_R_SUCCESS for any
+ * element of 'list'.
+ *
+ * Requires:
+ *
+ * \li 'config' is not NULL
+ * \li 'callback' is not NULL
+ *
+ * Returns:
+ *
+ * \li #ISC_R_SUCCESS if 'callback' returned #ISC_R_SUCCESS for all elements of
+ * 'list'
+ * \li first 'callback' return value which was not #ISC_R_SUCCESS otherwise
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/duration.h b/lib/isccfg/include/isccfg/duration.h
new file mode 100644
index 0000000..bd0c35b
--- /dev/null
+++ b/lib/isccfg/include/isccfg/duration.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
+
+/*! \file */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lang.h>
+#include <isc/types.h>
+
+ISC_LANG_BEGINDECLS
+
+#define CFG_DURATION_MAXLEN 80
+
+/*%
+ * A configuration object to store ISO 8601 durations.
+ */
+typedef struct isccfg_duration {
+ /*
+ * The duration is stored in multiple parts:
+ * [0] Years
+ * [1] Months
+ * [2] Weeks
+ * [3] Days
+ * [4] Hours
+ * [5] Minutes
+ * [6] Seconds
+ */
+ uint32_t parts[7];
+ bool iso8601;
+ bool unlimited;
+} isccfg_duration_t;
+
+isc_result_t
+isccfg_duration_fromtext(isc_textregion_t *source, isccfg_duration_t *duration);
+/*%<
+ * Converts an ISO 8601 duration style value.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_BADNUMBER
+ */
+
+isc_result_t
+isccfg_parse_duration(isc_textregion_t *source, isccfg_duration_t *duration);
+/*%<
+ * Converts a duration string to a ISO 8601 duration.
+ * If the string does not start with a P (or p), fall back to TTL-style value.
+ * In that case the duration will be treated in seconds only.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS
+ *\li DNS_R_BADNUMBER
+ *\li DNS_R_BADTTL
+ */
+
+uint32_t
+isccfg_duration_toseconds(const isccfg_duration_t *duration);
+/*%<
+ * Converts an ISO 8601 duration to seconds.
+ * The conversion is approximate:
+ * - Months will be treated as 31 days.
+ * - Years will be treated as 365 days.
+ *
+ * Notes:
+ *\li If the duration in seconds is greater than UINT32_MAX, the return value
+ * will be UINT32_MAX.
+ *
+ * Returns:
+ *\li The duration in seconds.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/grammar.h b/lib/isccfg/include/isccfg/grammar.h
new file mode 100644
index 0000000..83482d9
--- /dev/null
+++ b/lib/isccfg/include/isccfg/grammar.h
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file isccfg/grammar.h */
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/lex.h>
+#include <isc/netaddr.h>
+#include <isc/region.h>
+#include <isc/sockaddr.h>
+#include <isc/types.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/duration.h>
+
+/*
+ * Definitions shared between the configuration parser
+ * and the grammars; not visible to users of the parser.
+ */
+
+/*% Clause may occur multiple times (e.g., "zone") */
+#define CFG_CLAUSEFLAG_MULTI 0x00000001
+/*% Clause is obsolete (logs a warning, but is not a fatal error) */
+#define CFG_CLAUSEFLAG_OBSOLETE 0x00000002
+/* obsolete: #define CFG_CLAUSEFLAG_NOTIMP 0x00000004 */
+/* obsolete: #define CFG_CLAUSEFLAG_NYI 0x00000008 */
+/* obsolete: #define CFG_CLAUSEFLAG_NEWDEFAULT 0x00000010 */
+/*%
+ * Clause needs to be interpreted during parsing
+ * by calling a callback function, like the
+ * "directory" option.
+ */
+#define CFG_CLAUSEFLAG_CALLBACK 0x00000020
+/*% An option that is only used in testing. */
+#define CFG_CLAUSEFLAG_TESTONLY 0x00000040
+/*% A configuration option that was not configured at compile time. */
+#define CFG_CLAUSEFLAG_NOTCONFIGURED 0x00000080
+/*% An option for an experimental feature. */
+#define CFG_CLAUSEFLAG_EXPERIMENTAL 0x00000100
+/*% An option that should be omited from the documentation */
+#define CFG_CLAUSEFLAG_NODOC 0x00000200
+/*% Clause will be obsolete in a future release (logs a warning) */
+#define CFG_CLAUSEFLAG_DEPRECATED 0x00000400
+/*% Clause has been obsolete so long that it's now a fatal error */
+#define CFG_CLAUSEFLAG_ANCIENT 0x00000800
+
+/*%
+ * Zone types for which a clause is valid:
+ * These share space with CFG_CLAUSEFLAG values, but count
+ * down from the top.
+ */
+#define CFG_ZONE_PRIMARY 0x80000000
+#define CFG_ZONE_SECONDARY 0x40000000
+#define CFG_ZONE_STUB 0x20000000
+#define CFG_ZONE_HINT 0x10000000
+#define CFG_ZONE_FORWARD 0x08000000
+#define CFG_ZONE_STATICSTUB 0x04000000
+#define CFG_ZONE_REDIRECT 0x02000000
+#define CFG_ZONE_DELEGATION 0x01000000
+#define CFG_ZONE_INVIEW 0x00800000
+#define CFG_ZONE_MIRROR 0x00400000
+
+typedef struct cfg_clausedef cfg_clausedef_t;
+typedef struct cfg_tuplefielddef cfg_tuplefielddef_t;
+typedef struct cfg_printer cfg_printer_t;
+typedef ISC_LIST(cfg_listelt_t) cfg_list_t;
+typedef struct cfg_map cfg_map_t;
+typedef struct cfg_rep cfg_rep_t;
+
+/*
+ * Function types for configuration object methods
+ */
+
+typedef isc_result_t (*cfg_parsefunc_t)(cfg_parser_t *, const cfg_type_t *type,
+ cfg_obj_t **);
+typedef void (*cfg_printfunc_t)(cfg_printer_t *, const cfg_obj_t *);
+typedef void (*cfg_docfunc_t)(cfg_printer_t *, const cfg_type_t *);
+typedef void (*cfg_freefunc_t)(cfg_parser_t *, cfg_obj_t *);
+
+/*
+ * Structure definitions
+ */
+
+/*%
+ * A configuration printer object. This is an abstract
+ * interface to a destination to which text can be printed
+ * by calling the function 'f'.
+ */
+struct cfg_printer {
+ void (*f)(void *closure, const char *text, int textlen);
+ void *closure;
+ int indent;
+ int flags;
+};
+
+/*% A clause definition. */
+struct cfg_clausedef {
+ const char *name;
+ cfg_type_t *type;
+ unsigned int flags;
+};
+
+/*% A tuple field definition. */
+struct cfg_tuplefielddef {
+ const char *name;
+ cfg_type_t *type;
+ unsigned int flags;
+};
+
+/*% A configuration object type definition. */
+struct cfg_type {
+ const char *name; /*%< For debugging purposes only */
+ cfg_parsefunc_t parse;
+ cfg_printfunc_t print;
+ cfg_docfunc_t doc; /*%< Print grammar description */
+ cfg_rep_t *rep; /*%< Data representation */
+ const void *of; /*%< Additional data for meta-types */
+};
+
+/*% A keyword-type definition, for things like "port <integer>". */
+typedef struct {
+ const char *name;
+ const cfg_type_t *type;
+} keyword_type_t;
+
+struct cfg_map {
+ cfg_obj_t *id; /*%< Used for 'named maps' like
+ * keys, zones, &c */
+ const cfg_clausedef_t *const *clausesets; /*%< The clauses that
+ * can occur in this map;
+ * used for printing */
+ isc_symtab_t *symtab;
+};
+
+typedef struct cfg_netprefix cfg_netprefix_t;
+
+struct cfg_netprefix {
+ isc_netaddr_t address; /* IP4/IP6 */
+ unsigned int prefixlen;
+};
+
+/*%
+ * A configuration data representation.
+ */
+struct cfg_rep {
+ const char *name; /*%< For debugging only */
+ cfg_freefunc_t free; /*%< How to free this kind of data. */
+};
+
+/*%
+ * A configuration object. This is the main building block
+ * of the configuration parse tree.
+ */
+
+struct cfg_obj {
+ const cfg_type_t *type;
+ union {
+ uint32_t uint32;
+ uint64_t uint64;
+ isc_textregion_t string; /*%< null terminated, too */
+ bool boolean;
+ cfg_map_t map;
+ cfg_list_t list;
+ cfg_obj_t **tuple;
+ isc_sockaddr_t sockaddr;
+ struct {
+ isc_sockaddr_t sockaddr;
+ int32_t dscp;
+ } sockaddrdscp;
+ cfg_netprefix_t netprefix;
+ isccfg_duration_t duration;
+ } value;
+ isc_refcount_t references; /*%< reference counter */
+ const char *file;
+ unsigned int line;
+ cfg_parser_t *pctx;
+};
+
+/*% A list element. */
+struct cfg_listelt {
+ cfg_obj_t *obj;
+ ISC_LINK(cfg_listelt_t) link;
+};
+
+/*% The parser object. */
+struct cfg_parser {
+ isc_mem_t *mctx;
+ isc_log_t *lctx;
+ isc_lex_t *lexer;
+ unsigned int errors;
+ unsigned int warnings;
+ isc_token_t token;
+
+ /*% We are at the end of all input. */
+ bool seen_eof;
+
+ /*% The current token has been pushed back. */
+ bool ungotten;
+
+ /*%
+ * The stack of currently active files, represented
+ * as a configuration list of configuration strings.
+ * The head is the top-level file, subsequent elements
+ * (if any) are the nested include files, and the
+ * last element is the file currently being parsed.
+ */
+ cfg_obj_t *open_files;
+
+ /*%
+ * Names of files that we have parsed and closed
+ * and were previously on the open_file list.
+ * We keep these objects around after closing
+ * the files because the file names may still be
+ * referenced from other configuration objects
+ * for use in reporting semantic errors after
+ * parsing is complete.
+ */
+ cfg_obj_t *closed_files;
+
+ /*%
+ * Name of a buffer being parsed; used only for
+ * logging.
+ */
+ char const *buf_name;
+
+ /*%
+ * Current line number. We maintain our own
+ * copy of this so that it is available even
+ * when a file has just been closed.
+ */
+ unsigned int line;
+
+ /*%
+ * Parser context flags, used for maintaining state
+ * from one token to the next.
+ */
+ unsigned int flags;
+
+ /*%< Reference counter */
+ isc_refcount_t references;
+
+ cfg_parsecallback_t callback;
+ void *callbackarg;
+};
+
+/* Parser context flags */
+#define CFG_PCTX_SKIP 0x1
+#define CFG_PCTX_NODEPRECATED 0x2
+
+/*@{*/
+/*%
+ * Flags defining whether to accept certain types of network addresses.
+ */
+#define CFG_ADDR_V4OK 0x00000001
+#define CFG_ADDR_V4PREFIXOK 0x00000002
+#define CFG_ADDR_V6OK 0x00000004
+#define CFG_ADDR_WILDOK 0x00000008
+#define CFG_ADDR_DSCPOK 0x00000010
+#define CFG_ADDR_PORTOK 0x00000020
+#define CFG_ADDR_MASK (CFG_ADDR_V6OK | CFG_ADDR_V4OK)
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined data representation types.
+ */
+extern cfg_rep_t cfg_rep_uint32;
+extern cfg_rep_t cfg_rep_uint64;
+extern cfg_rep_t cfg_rep_string;
+extern cfg_rep_t cfg_rep_boolean;
+extern cfg_rep_t cfg_rep_map;
+extern cfg_rep_t cfg_rep_list;
+extern cfg_rep_t cfg_rep_tuple;
+extern cfg_rep_t cfg_rep_sockaddr;
+extern cfg_rep_t cfg_rep_netprefix;
+extern cfg_rep_t cfg_rep_void;
+extern cfg_rep_t cfg_rep_fixedpoint;
+extern cfg_rep_t cfg_rep_percentage;
+extern cfg_rep_t cfg_rep_duration;
+/*@}*/
+
+/*@{*/
+/*%
+ * Predefined configuration object types.
+ */
+extern cfg_type_t cfg_type_boolean;
+extern cfg_type_t cfg_type_uint32;
+extern cfg_type_t cfg_type_uint64;
+extern cfg_type_t cfg_type_qstring;
+extern cfg_type_t cfg_type_astring;
+extern cfg_type_t cfg_type_ustring;
+extern cfg_type_t cfg_type_sstring;
+extern cfg_type_t cfg_type_bracketed_aml;
+extern cfg_type_t cfg_type_bracketed_text;
+extern cfg_type_t cfg_type_optional_bracketed_text;
+extern cfg_type_t cfg_type_keyref;
+extern cfg_type_t cfg_type_sockaddr;
+extern cfg_type_t cfg_type_sockaddrdscp;
+extern cfg_type_t cfg_type_netaddr;
+extern cfg_type_t cfg_type_netaddr4;
+extern cfg_type_t cfg_type_netaddr4wild;
+extern cfg_type_t cfg_type_netaddr6;
+extern cfg_type_t cfg_type_netaddr6wild;
+extern cfg_type_t cfg_type_netprefix;
+extern cfg_type_t cfg_type_void;
+extern cfg_type_t cfg_type_token;
+extern cfg_type_t cfg_type_unsupported;
+extern cfg_type_t cfg_type_fixedpoint;
+extern cfg_type_t cfg_type_percentage;
+extern cfg_type_t cfg_type_duration;
+extern cfg_type_t cfg_type_duration_or_unlimited;
+/*@}*/
+
+isc_result_t
+cfg_gettoken(cfg_parser_t *pctx, int options);
+
+isc_result_t
+cfg_peektoken(cfg_parser_t *pctx, int options);
+
+void
+cfg_ungettoken(cfg_parser_t *pctx);
+
+#define CFG_LEXOPT_QSTRING (ISC_LEXOPT_QSTRING | ISC_LEXOPT_QSTRINGMULTILINE)
+
+isc_result_t
+cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+void
+cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u);
+
+isc_result_t
+cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na);
+
+void
+cfg_print_rawaddr(cfg_printer_t *pctx, const isc_netaddr_t *na);
+
+bool
+cfg_lookingat_netaddr(cfg_parser_t *pctx, unsigned int flags);
+
+isc_result_t
+cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port);
+
+isc_result_t
+cfg_parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_sockaddr(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_print_boolean(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_special(cfg_parser_t *pctx, int special);
+/*%< Parse a required special character 'special'. */
+
+isc_result_t
+cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+isc_result_t
+cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+isc_result_t
+cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
+ cfg_listelt_t **ret);
+
+isc_result_t
+cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype, cfg_obj_t **ret);
+
+void
+cfg_doc_enum_or_other(cfg_printer_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype);
+
+void
+cfg_print_chars(cfg_printer_t *pctx, const char *text, int len);
+/*%< Print 'len' characters at 'text' */
+
+void
+cfg_print_cstr(cfg_printer_t *pctx, const char *s);
+/*%< Print the null-terminated string 's' */
+
+isc_result_t
+cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+isc_result_t
+cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type);
+
+isc_result_t
+cfg_parse_fixedpoint(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_fixedpoint(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+void
+cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+isc_result_t
+cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+void
+cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+void
+cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type);
+/*%<
+ * Print a description of the grammar of an arbitrary configuration
+ * type 'type'
+ */
+
+void
+cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type);
+/*%<
+ * Document the type 'type' as a terminal by printing its
+ * name in angle brackets, e.g., &lt;uint32>.
+ */
+
+void
+cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+/*!
+ * Pass one of these flags to cfg_parser_error() to include the
+ * token text in log message.
+ */
+#define CFG_LOG_NEAR 0x00000001 /*%< Say "near <token>" */
+#define CFG_LOG_BEFORE 0x00000002 /*%< Say "before <token>" */
+#define CFG_LOG_NOPREP 0x00000004 /*%< Say just "<token>" */
+
+void
+cfg_parser_warning(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+bool
+cfg_is_enum(const char *s, const char *const *enums);
+/*%< Return true iff the string 's' is one of the strings in 'enums' */
+
+bool
+cfg_clause_validforzone(const char *name, unsigned int ztype);
+/*%<
+ * Check whether an option is legal for the specified zone type.
+ */
+
+void
+cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure);
+/*%<
+ * Print a summary of the grammar of the zone type represented by
+ * 'zonetype'.
+ */
+
+void
+cfg_print_clauseflags(cfg_printer_t *pctx, unsigned int flags);
+/*%<
+ * Print clause flags (e.g. "obsolete", "not implemented", etc) in
+ * human readable form
+ */
+
+void
+cfg_print_indent(cfg_printer_t *pctx);
+/*%<
+ * Print the necessary indent required by the current settings of 'pctx'.
+ */
diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h
new file mode 100644
index 0000000..7b1e075
--- /dev/null
+++ b/lib/isccfg/include/isccfg/kaspconf.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <isccfg/cfg.h>
+
+/***
+ *** Functions
+ ***/
+
+ISC_LANG_BEGINDECLS
+
+isc_result_t
+cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
+ isc_mem_t *mctx, isc_log_t *logctx,
+ dns_kasplist_t *kasplist, dns_kasp_t **kaspp);
+/*%<
+ * Create and configure a KASP. If 'default_kasp' is not NULL, the built-in
+ * default configuration is used to set values that are not explicitly set in
+ * the policy. If a 'kasplist' is provided, a lookup happens and if a KASP
+ * already exists with the same name, no new KASP is created, and no attach to
+ * 'kaspp' happens.
+ *
+ * Requires:
+ *
+ *\li 'name' is either NULL, or a valid C string.
+ *
+ *\li 'mctx' is a valid memory context.
+ *
+ *\li 'logctx' is a valid logging context.
+ *
+ *\li kaspp != NULL && *kaspp == NULL
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS If creating and configuring the KASP succeeds.
+ *\li #ISC_R_EXISTS If 'kasplist' already has a kasp structure with 'name'.
+ *\li #ISC_R_NOMEMORY
+ *
+ *\li Other errors are possible.
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/log.h b/lib/isccfg/include/isccfg/log.h
new file mode 100644
index 0000000..8af3095
--- /dev/null
+++ b/lib/isccfg/include/isccfg/log.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
+
+/*! \file isccfg/log.h */
+
+#include <isc/lang.h>
+#include <isc/log.h>
+
+extern isc_logcategory_t cfg_categories[];
+extern isc_logmodule_t cfg_modules[];
+
+#define CFG_LOGCATEGORY_CONFIG (&cfg_categories[0])
+
+#define CFG_LOGMODULE_PARSER (&cfg_modules[0])
+
+ISC_LANG_BEGINDECLS
+
+void
+cfg_log_init(isc_log_t *lctx);
+/*%<
+ * Make the libisccfg categories and modules available for use with the
+ * ISC logging library.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li cfg_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().
+ */
+
+ISC_LANG_ENDDECLS
diff --git a/lib/isccfg/include/isccfg/namedconf.h b/lib/isccfg/include/isccfg/namedconf.h
new file mode 100644
index 0000000..f2d0145
--- /dev/null
+++ b/lib/isccfg/include/isccfg/namedconf.h
@@ -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.
+ */
+
+#pragma once
+
+/*! \file isccfg/namedconf.h
+ * \brief
+ * This module defines the named.conf, rndc.conf, and rndc.key grammars.
+ */
+
+#include <isccfg/cfg.h>
+
+/*
+ * Configuration object types.
+ */
+extern cfg_type_t cfg_type_namedconf;
+/*%< A complete named.conf file. */
+
+extern cfg_type_t cfg_type_bindkeys;
+/*%< A bind.keys file. */
+
+extern cfg_type_t cfg_type_newzones;
+/*%< A new-zones file (for zones added by 'rndc addzone'). */
+
+extern cfg_type_t cfg_type_addzoneconf;
+/*%< A single zone passed via the addzone rndc command. */
+
+extern cfg_type_t cfg_type_rndcconf;
+/*%< A complete rndc.conf file. */
+
+extern cfg_type_t cfg_type_rndckey;
+/*%< A complete rndc.key file. */
+
+extern cfg_type_t cfg_type_sessionkey;
+/*%< A complete session.key file. */
+
+extern cfg_type_t cfg_type_keyref;
+/*%< A key reference, used as an ACL element */
+
+/*%< Zone options */
+extern cfg_type_t cfg_type_zoneopts;
+
+/*%< DNSSEC Key and Signing Policy options */
+extern cfg_type_t cfg_type_dnssecpolicyopts;
diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c
new file mode 100644
index 0000000..ba36b0d
--- /dev/null
+++ b/lib/isccfg/kaspconf.c
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdlib.h>
+
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/nsec3.h>
+#include <dns/secalg.h>
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/duration.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#define DEFAULT_NSEC3PARAM_ITER 0
+#define DEFAULT_NSEC3PARAM_SALTLEN 0
+
+/*
+ * Utility function for getting a configuration option.
+ */
+static isc_result_t
+confget(cfg_obj_t const *const *maps, const char *name, const cfg_obj_t **obj) {
+ for (size_t i = 0;; i++) {
+ if (maps[i] == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (cfg_map_get(maps[i], name, obj) == ISC_R_SUCCESS) {
+ return (ISC_R_SUCCESS);
+ }
+ }
+}
+
+/*
+ * Utility function for parsing durations from string.
+ */
+static uint32_t
+parse_duration(const char *str) {
+ uint32_t time = 0;
+ isccfg_duration_t duration;
+ isc_result_t result;
+ isc_textregion_t tr;
+
+ DE_CONST(str, tr.base);
+ tr.length = strlen(tr.base);
+ result = isccfg_parse_duration(&tr, &duration);
+ if (result == ISC_R_SUCCESS) {
+ time = isccfg_duration_toseconds(&duration);
+ }
+ return (time);
+}
+
+/*
+ * Utility function for configuring durations.
+ */
+static uint32_t
+get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) {
+ const cfg_obj_t *obj;
+ isc_result_t result;
+ obj = NULL;
+
+ result = confget(maps, option, &obj);
+ if (result == ISC_R_NOTFOUND) {
+ return (parse_duration(dfl));
+ }
+ INSIST(result == ISC_R_SUCCESS);
+ return (cfg_obj_asduration(obj));
+}
+
+/*
+ * Create a new kasp key derived from configuration.
+ */
+static isc_result_t
+cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
+ isc_log_t *logctx, uint32_t ksk_min_lifetime,
+ uint32_t zsk_min_lifetime) {
+ isc_result_t result;
+ dns_kasp_key_t *key = NULL;
+
+ /* Create a new key reference. */
+ result = dns_kasp_key_create(kasp, &key);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (config == NULL) {
+ /* We are creating a key reference for the default kasp. */
+ key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK;
+ key->lifetime = 0; /* unlimited */
+ key->algorithm = DNS_KEYALG_ECDSA256;
+ key->length = -1;
+ } else {
+ const char *rolestr = NULL;
+ const cfg_obj_t *obj = NULL;
+ isc_consttextregion_t alg;
+ bool error = false;
+
+ rolestr = cfg_obj_asstring(cfg_tuple_get(config, "role"));
+ if (strcmp(rolestr, "ksk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_KSK;
+ } else if (strcmp(rolestr, "zsk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ } else if (strcmp(rolestr, "csk") == 0) {
+ key->role |= DNS_KASP_KEY_ROLE_KSK;
+ key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+
+ key->lifetime = 0; /* unlimited */
+ obj = cfg_tuple_get(config, "lifetime");
+ if (cfg_obj_isduration(obj)) {
+ key->lifetime = cfg_obj_asduration(obj);
+ }
+ if (key->lifetime > 0) {
+ if (key->lifetime < 30 * (24 * 3600)) {
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: key lifetime is "
+ "shorter than 30 days");
+ }
+ if ((key->role & DNS_KASP_KEY_ROLE_KSK) != 0 &&
+ key->lifetime <= ksk_min_lifetime)
+ {
+ error = true;
+ }
+ if ((key->role & DNS_KASP_KEY_ROLE_ZSK) != 0 &&
+ key->lifetime <= zsk_min_lifetime)
+ {
+ error = true;
+ }
+ if (error) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: key lifetime is "
+ "shorter than the time it takes to "
+ "do a rollover");
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
+ }
+
+ obj = cfg_tuple_get(config, "algorithm");
+ alg.base = cfg_obj_asstring(obj);
+ alg.length = strlen(alg.base);
+ result = dns_secalg_fromtext(&key->algorithm,
+ (isc_textregion_t *)&alg);
+ if (result != ISC_R_SUCCESS) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: bad algorithm %s",
+ alg.base);
+ result = DNS_R_BADALG;
+ goto cleanup;
+ }
+
+ obj = cfg_tuple_get(config, "length");
+ if (cfg_obj_isuint32(obj)) {
+ uint32_t min, size;
+ size = cfg_obj_asuint32(obj);
+
+ switch (key->algorithm) {
+ case DNS_KEYALG_RSASHA1:
+ case DNS_KEYALG_NSEC3RSASHA1:
+ case DNS_KEYALG_RSASHA256:
+ case DNS_KEYALG_RSASHA512:
+ min = DNS_KEYALG_RSASHA512 ? 1024 : 512;
+ if (size < min || size > 4096) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: key with "
+ "algorithm %s has invalid "
+ "key length %u",
+ alg.base, size);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ break;
+ case DNS_KEYALG_ECDSA256:
+ case DNS_KEYALG_ECDSA384:
+ case DNS_KEYALG_ED25519:
+ case DNS_KEYALG_ED448:
+ cfg_obj_log(obj, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: key algorithm %s "
+ "has predefined length; ignoring "
+ "length value %u",
+ alg.base, size);
+ default:
+ break;
+ }
+
+ key->length = size;
+ }
+ }
+
+ dns_kasp_addkey(kasp, key);
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ dns_kasp_key_destroy(key);
+ return (result);
+}
+
+static isc_result_t
+cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp,
+ isc_log_t *logctx) {
+ dns_kasp_key_t *kkey;
+ unsigned int min_keysize = 4096;
+ const cfg_obj_t *obj = NULL;
+ uint32_t iter = DEFAULT_NSEC3PARAM_ITER;
+ uint32_t saltlen = DEFAULT_NSEC3PARAM_SALTLEN;
+ uint32_t badalg = 0;
+ bool optout = false;
+ isc_result_t ret = ISC_R_SUCCESS;
+
+ /* How many iterations. */
+ obj = cfg_tuple_get(config, "iterations");
+ if (cfg_obj_isuint32(obj)) {
+ iter = cfg_obj_asuint32(obj);
+ }
+ dns_kasp_freeze(kasp);
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ unsigned int keysize = dns_kasp_key_size(kkey);
+ uint32_t keyalg = dns_kasp_key_algorithm(kkey);
+
+ if (keysize < min_keysize) {
+ min_keysize = keysize;
+ }
+
+ /* NSEC3 cannot be used with certain key algorithms. */
+ if (keyalg == DNS_KEYALG_RSAMD5 || keyalg == DNS_KEYALG_DH ||
+ keyalg == DNS_KEYALG_DSA || keyalg == DNS_KEYALG_RSASHA1)
+ {
+ badalg = keyalg;
+ }
+ }
+ dns_kasp_thaw(kasp);
+
+ if (badalg > 0) {
+ char algstr[DNS_SECALG_FORMATSIZE];
+ dns_secalg_format((dns_secalg_t)badalg, algstr, sizeof(algstr));
+ cfg_obj_log(
+ obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: cannot use nsec3 with algorithm '%s'",
+ algstr);
+ return (DNS_R_NSEC3BADALG);
+ }
+
+ if (iter > dns_nsec3_maxiterations()) {
+ ret = DNS_R_NSEC3ITERRANGE;
+ }
+
+ if (ret == DNS_R_NSEC3ITERRANGE) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: nsec3 iterations value %u "
+ "out of range",
+ iter);
+ return (ret);
+ }
+
+ /* Opt-out? */
+ obj = cfg_tuple_get(config, "optout");
+ if (cfg_obj_isboolean(obj)) {
+ optout = cfg_obj_asboolean(obj);
+ }
+
+ /* Salt */
+ obj = cfg_tuple_get(config, "salt-length");
+ if (cfg_obj_isuint32(obj)) {
+ saltlen = cfg_obj_asuint32(obj);
+ }
+ if (saltlen > 0xff) {
+ cfg_obj_log(obj, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: nsec3 salt length %u too high",
+ saltlen);
+ return (DNS_R_NSEC3SALTRANGE);
+ }
+
+ dns_kasp_setnsec3param(kasp, iter, optout, saltlen);
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp,
+ isc_mem_t *mctx, isc_log_t *logctx,
+ dns_kasplist_t *kasplist, dns_kasp_t **kaspp) {
+ isc_result_t result;
+ const cfg_obj_t *maps[2];
+ const cfg_obj_t *koptions = NULL;
+ const cfg_obj_t *keys = NULL;
+ const cfg_obj_t *nsec3 = NULL;
+ const cfg_listelt_t *element = NULL;
+ const char *kaspname = NULL;
+ dns_kasp_t *kasp = NULL;
+ size_t i = 0;
+ uint32_t sigrefresh = 0, sigvalidity = 0;
+ uint32_t dnskeyttl = 0, dsttl = 0, maxttl = 0;
+ uint32_t publishsafety = 0, retiresafety = 0;
+ uint32_t zonepropdelay = 0, parentpropdelay = 0;
+ uint32_t ipub = 0, iret = 0;
+ uint32_t ksk_min_lifetime = 0, zsk_min_lifetime = 0;
+
+ REQUIRE(config != NULL);
+ REQUIRE(kaspp != NULL && *kaspp == NULL);
+
+ kaspname = cfg_obj_asstring(cfg_tuple_get(config, "name"));
+ INSIST(kaspname != NULL);
+
+ cfg_obj_log(config, logctx, ISC_LOG_DEBUG(1),
+ "dnssec-policy: load policy '%s'", kaspname);
+
+ result = dns_kasplist_find(kasplist, kaspname, &kasp);
+
+ if (result == ISC_R_SUCCESS) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: duplicately named policy found '%s'",
+ kaspname);
+ dns_kasp_detach(&kasp);
+ return (ISC_R_EXISTS);
+ }
+ if (result != ISC_R_NOTFOUND) {
+ return (result);
+ }
+
+ /* No kasp with configured name was found in list, create new one. */
+ INSIST(kasp == NULL);
+ result = dns_kasp_create(mctx, kaspname, &kasp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ INSIST(kasp != NULL);
+
+ /* Now configure. */
+ INSIST(DNS_KASP_VALID(kasp));
+
+ if (config != NULL) {
+ koptions = cfg_tuple_get(config, "options");
+ maps[i++] = koptions;
+ }
+ maps[i] = NULL;
+
+ /* Configuration: Signatures */
+ sigrefresh = get_duration(maps, "signatures-refresh",
+ DNS_KASP_SIG_REFRESH);
+ dns_kasp_setsigrefresh(kasp, sigrefresh);
+
+ sigvalidity = get_duration(maps, "signatures-validity-dnskey",
+ DNS_KASP_SIG_VALIDITY_DNSKEY);
+ if (sigrefresh >= (sigvalidity * 0.9)) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: policy '%s' signatures-refresh must be "
+ "at most 90%% of the signatures-validity-dnskey",
+ kaspname);
+ result = ISC_R_FAILURE;
+ }
+ dns_kasp_setsigvalidity_dnskey(kasp, sigvalidity);
+
+ sigvalidity = get_duration(maps, "signatures-validity",
+ DNS_KASP_SIG_VALIDITY);
+ if (sigrefresh >= (sigvalidity * 0.9)) {
+ cfg_obj_log(
+ config, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: policy '%s' signatures-refresh must be "
+ "at most 90%% of the signatures-validity",
+ kaspname);
+ result = ISC_R_FAILURE;
+ }
+ dns_kasp_setsigvalidity(kasp, sigvalidity);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /* Configuration: Zone settings */
+ maxttl = get_duration(maps, "max-zone-ttl", DNS_KASP_ZONE_MAXTTL);
+ dns_kasp_setzonemaxttl(kasp, maxttl);
+
+ zonepropdelay = get_duration(maps, "zone-propagation-delay",
+ DNS_KASP_ZONE_PROPDELAY);
+ dns_kasp_setzonepropagationdelay(kasp, zonepropdelay);
+
+ /* Configuration: Parent settings */
+ dsttl = get_duration(maps, "parent-ds-ttl", DNS_KASP_DS_TTL);
+ dns_kasp_setdsttl(kasp, dsttl);
+
+ parentpropdelay = get_duration(maps, "parent-propagation-delay",
+ DNS_KASP_PARENT_PROPDELAY);
+ dns_kasp_setparentpropagationdelay(kasp, parentpropdelay);
+
+ /* Configuration: Keys */
+ dnskeyttl = get_duration(maps, "dnskey-ttl", DNS_KASP_KEY_TTL);
+ dns_kasp_setdnskeyttl(kasp, dnskeyttl);
+
+ publishsafety = get_duration(maps, "publish-safety",
+ DNS_KASP_PUBLISH_SAFETY);
+ dns_kasp_setpublishsafety(kasp, publishsafety);
+
+ retiresafety = get_duration(maps, "retire-safety",
+ DNS_KASP_RETIRE_SAFETY);
+ dns_kasp_setretiresafety(kasp, retiresafety);
+
+ dns_kasp_setpurgekeys(
+ kasp, get_duration(maps, "purge-keys", DNS_KASP_PURGE_KEYS));
+
+ ipub = dnskeyttl + publishsafety + zonepropdelay;
+ iret = dsttl + retiresafety + parentpropdelay;
+ ksk_min_lifetime = ISC_MAX(ipub, iret);
+
+ iret = (sigvalidity - sigrefresh) + maxttl + retiresafety +
+ zonepropdelay;
+ zsk_min_lifetime = ISC_MAX(ipub, iret);
+
+ (void)confget(maps, "keys", &keys);
+ if (keys != NULL) {
+ char role[256] = { 0 };
+ bool warn[256][2] = { { false } };
+ dns_kasp_key_t *kkey = NULL;
+
+ for (element = cfg_list_first(keys); element != NULL;
+ element = cfg_list_next(element))
+ {
+ cfg_obj_t *kobj = cfg_listelt_value(element);
+ result = cfg_kaspkey_fromconfig(kobj, kasp, logctx,
+ ksk_min_lifetime,
+ zsk_min_lifetime);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ dns_kasp_freeze(kasp);
+ for (kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); kkey != NULL;
+ kkey = ISC_LIST_NEXT(kkey, link))
+ {
+ uint32_t keyalg = dns_kasp_key_algorithm(kkey);
+ INSIST(keyalg < ARRAY_SIZE(role));
+
+ if (dns_kasp_key_zsk(kkey)) {
+ if ((role[keyalg] & DNS_KASP_KEY_ROLE_ZSK) != 0)
+ {
+ warn[keyalg][0] = true;
+ }
+ role[keyalg] |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+
+ if (dns_kasp_key_ksk(kkey)) {
+ if ((role[keyalg] & DNS_KASP_KEY_ROLE_KSK) != 0)
+ {
+ warn[keyalg][1] = true;
+ }
+ role[keyalg] |= DNS_KASP_KEY_ROLE_KSK;
+ }
+ }
+ dns_kasp_thaw(kasp);
+ for (i = 0; i < ARRAY_SIZE(role); i++) {
+ if (role[i] == 0) {
+ continue;
+ }
+ if (role[i] !=
+ (DNS_KASP_KEY_ROLE_ZSK | DNS_KASP_KEY_ROLE_KSK))
+ {
+ cfg_obj_log(keys, logctx, ISC_LOG_ERROR,
+ "dnssec-policy: algorithm %zu "
+ "requires both KSK and ZSK roles",
+ i);
+ result = ISC_R_FAILURE;
+ }
+ if (warn[i][0]) {
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: algorithm %zu has "
+ "multiple keys with ZSK role",
+ i);
+ }
+ if (warn[i][1]) {
+ cfg_obj_log(keys, logctx, ISC_LOG_WARNING,
+ "dnssec-policy: algorithm %zu has "
+ "multiple keys with KSK role",
+ i);
+ }
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else if (default_kasp) {
+ dns_kasp_key_t *key, *new_key;
+ /*
+ * If there are no specific keys configured in the policy,
+ * inherit from the default policy (except for the built-in
+ * "insecure" policy).
+ */
+ for (key = ISC_LIST_HEAD(dns_kasp_keys(default_kasp));
+ key != NULL; key = ISC_LIST_NEXT(key, link))
+ {
+ /* Create a new key reference. */
+ new_key = NULL;
+ result = dns_kasp_key_create(kasp, &new_key);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (dns_kasp_key_ksk(key)) {
+ new_key->role |= DNS_KASP_KEY_ROLE_KSK;
+ }
+ if (dns_kasp_key_zsk(key)) {
+ new_key->role |= DNS_KASP_KEY_ROLE_ZSK;
+ }
+ new_key->lifetime = dns_kasp_key_lifetime(key);
+ new_key->algorithm = dns_kasp_key_algorithm(key);
+ new_key->length = dns_kasp_key_size(key);
+ dns_kasp_addkey(kasp, new_key);
+ }
+ }
+
+ if (strcmp(kaspname, "insecure") == 0) {
+ /* "dnssec-policy insecure": key list must be empty */
+ INSIST(dns_kasp_keylist_empty(kasp));
+ } else if (default_kasp != NULL) {
+ /* There must be keys configured. */
+ INSIST(!(dns_kasp_keylist_empty(kasp)));
+ }
+
+ /* Configuration: NSEC3 */
+ (void)confget(maps, "nsec3param", &nsec3);
+ if (nsec3 == NULL) {
+ if (default_kasp != NULL && dns_kasp_nsec3(default_kasp)) {
+ dns_kasp_setnsec3param(
+ kasp, dns_kasp_nsec3iter(default_kasp),
+ (dns_kasp_nsec3flags(default_kasp) == 0x01),
+ dns_kasp_nsec3saltlen(default_kasp));
+ } else {
+ dns_kasp_setnsec3(kasp, false);
+ }
+ } else {
+ dns_kasp_setnsec3(kasp, true);
+ result = cfg_nsec3param_fromconfig(nsec3, kasp, logctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ /* Append it to the list for future lookups. */
+ ISC_LIST_APPEND(*kasplist, kasp, link);
+ INSIST(!(ISC_LIST_EMPTY(*kasplist)));
+
+ /* Success: Attach the kasp to the pointer and return. */
+ dns_kasp_attach(kasp, kaspp);
+
+ /* Don't detach as kasp is on '*kasplist' */
+ return (ISC_R_SUCCESS);
+
+cleanup:
+
+ /* Something bad happened, detach (destroys kasp) and return error. */
+ dns_kasp_detach(&kasp);
+ return (result);
+}
diff --git a/lib/isccfg/log.c b/lib/isccfg/log.c
new file mode 100644
index 0000000..22979ef
--- /dev/null
+++ b/lib/isccfg/log.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <isc/util.h>
+
+#include <isccfg/log.h>
+
+/*%
+ * When adding a new category, be sure to add the appropriate
+ * \#define to <isccfg/log.h>.
+ */
+isc_logcategory_t cfg_categories[] = { { "config", 0 }, { NULL, 0 } };
+
+/*%
+ * When adding a new module, be sure to add the appropriate
+ * \#define to <isccfg/log.h>.
+ */
+isc_logmodule_t cfg_modules[] = { { "isccfg/parser", 0 }, { NULL, 0 } };
+
+void
+cfg_log_init(isc_log_t *lctx) {
+ REQUIRE(lctx != NULL);
+
+ isc_log_registercategories(lctx, cfg_categories);
+ isc_log_registermodules(lctx, cfg_modules);
+}
diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c
new file mode 100644
index 0000000..4e4c098
--- /dev/null
+++ b/lib/isccfg/namedconf.c
@@ -0,0 +1,3998 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <string.h>
+
+#include <isc/lex.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/log.h>
+#include <isccfg/namedconf.h>
+
+#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base)
+
+/*% Check a return value. */
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+/*% Clean up a configuration object if non-NULL. */
+#define CLEANUP_OBJ(obj) \
+ do { \
+ if ((obj) != NULL) \
+ cfg_obj_destroy(pctx, &(obj)); \
+ } while (0)
+
+/*%
+ * Forward declarations of static functions.
+ */
+
+static isc_result_t
+parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static isc_result_t
+parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+static isc_result_t
+parse_updatepolicy(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+static void
+print_updatepolicy(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_updatepolicy(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static void
+print_keyvalue(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static void
+doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static isc_result_t
+cfg_parse_kv_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+cfg_print_kv_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+cfg_doc_kv_tuple(cfg_printer_t *pctx, const cfg_type_t *type);
+
+static cfg_type_t cfg_type_acl;
+static cfg_type_t cfg_type_bracketed_dscpsockaddrlist;
+static cfg_type_t cfg_type_bracketed_namesockaddrkeylist;
+static cfg_type_t cfg_type_bracketed_netaddrlist;
+static cfg_type_t cfg_type_bracketed_sockaddrnameportlist;
+static cfg_type_t cfg_type_bracketed_http_endpoint_list;
+static cfg_type_t cfg_type_controls;
+static cfg_type_t cfg_type_controls_sockaddr;
+static cfg_type_t cfg_type_destinationlist;
+static cfg_type_t cfg_type_dialuptype;
+static cfg_type_t cfg_type_dlz;
+static cfg_type_t cfg_type_dnssecpolicy;
+static cfg_type_t cfg_type_dnstap;
+static cfg_type_t cfg_type_dnstapoutput;
+static cfg_type_t cfg_type_dyndb;
+static cfg_type_t cfg_type_http_description;
+static cfg_type_t cfg_type_ixfrdifftype;
+static cfg_type_t cfg_type_ixfrratio;
+static cfg_type_t cfg_type_key;
+static cfg_type_t cfg_type_logfile;
+static cfg_type_t cfg_type_logging;
+static cfg_type_t cfg_type_logseverity;
+static cfg_type_t cfg_type_logsuffix;
+static cfg_type_t cfg_type_logversions;
+static cfg_type_t cfg_type_remoteselement;
+static cfg_type_t cfg_type_maxduration;
+static cfg_type_t cfg_type_minimal;
+static cfg_type_t cfg_type_nameportiplist;
+static cfg_type_t cfg_type_notifytype;
+static cfg_type_t cfg_type_optional_allow;
+static cfg_type_t cfg_type_optional_class;
+static cfg_type_t cfg_type_optional_dscp;
+static cfg_type_t cfg_type_optional_facility;
+static cfg_type_t cfg_type_optional_keyref;
+static cfg_type_t cfg_type_optional_port;
+static cfg_type_t cfg_type_optional_uint32;
+static cfg_type_t cfg_type_optional_tls;
+static cfg_type_t cfg_type_options;
+static cfg_type_t cfg_type_plugin;
+static cfg_type_t cfg_type_portiplist;
+static cfg_type_t cfg_type_printtime;
+static cfg_type_t cfg_type_qminmethod;
+static cfg_type_t cfg_type_querysource4;
+static cfg_type_t cfg_type_querysource6;
+static cfg_type_t cfg_type_querysource;
+static cfg_type_t cfg_type_server;
+static cfg_type_t cfg_type_server_key_kludge;
+static cfg_type_t cfg_type_size;
+static cfg_type_t cfg_type_sizenodefault;
+static cfg_type_t cfg_type_sizeorpercent;
+static cfg_type_t cfg_type_sizeval;
+static cfg_type_t cfg_type_sockaddr4wild;
+static cfg_type_t cfg_type_sockaddr6wild;
+static cfg_type_t cfg_type_statschannels;
+static cfg_type_t cfg_type_tlsconf;
+static cfg_type_t cfg_type_view;
+static cfg_type_t cfg_type_viewopts;
+static cfg_type_t cfg_type_zone;
+
+/*% tkey-dhkey */
+
+static cfg_tuplefielddef_t tkey_dhkey_fields[] = {
+ { "name", &cfg_type_qstring, 0 },
+ { "keyid", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_tkey_dhkey = { "tkey-dhkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, tkey_dhkey_fields };
+
+/*% listen-on */
+
+static cfg_tuplefielddef_t listenon_tuple_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_OBSOLETE | CFG_CLAUSEFLAG_NODOC },
+ { "tls", &cfg_type_astring, 0 },
+#if HAVE_LIBNGHTTP2
+ { "http", &cfg_type_astring, 0 },
+#else
+ { "http", &cfg_type_astring, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_listen_tuple = {
+ "listenon tuple", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, listenon_tuple_fields
+};
+
+static cfg_tuplefielddef_t listenon_fields[] = {
+ { "tuple", &cfg_type_listen_tuple, 0 },
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_listenon = { "listenon", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, listenon_fields };
+
+/*% acl */
+
+/*
+ * Encrypted transfer related definitions
+ */
+
+static cfg_tuplefielddef_t cfg_transport_acl_tuple_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "transport", &cfg_type_astring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_transport_acl_tuple = {
+ "transport-acl tuple", cfg_parse_kv_tuple,
+ cfg_print_kv_tuple, cfg_doc_kv_tuple,
+ &cfg_rep_tuple, cfg_transport_acl_tuple_fields
+};
+
+static cfg_tuplefielddef_t cfg_transport_acl_fields[] = {
+ { "port-transport", &cfg_transport_acl_tuple, 0 },
+ { "aml", &cfg_type_bracketed_aml, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_transport_acl = {
+ "transport-acl", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, cfg_transport_acl_fields
+};
+
+/*
+ * NOTE: To enable syntax which allows specifying port and protocol,
+ * replace 'cfg_type_bracketed_aml' with
+ * 'cfg_type_transport_acl'.
+ *
+ * Example: acl port 853 protocol tls { ... };
+ */
+static cfg_tuplefielddef_t acl_fields[] = { { "name", &cfg_type_astring, 0 },
+ { "value", &cfg_type_bracketed_aml,
+ 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_type_t cfg_type_acl = { "acl", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, acl_fields };
+
+/*% remote servers, used for primaries and parental agents */
+static cfg_tuplefielddef_t remotes_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_remoteservers = { "remote-servers", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, remotes_fields };
+
+/*%
+ * "sockaddrkeylist", a list of socket addresses with optional keys
+ * and an optional default port, as used in the remote-servers option.
+ * E.g.,
+ * "port 1234 { myservers; 10.0.0.1 key foo; 1::2 port 69; }"
+ */
+
+static cfg_tuplefielddef_t namesockaddrkey_fields[] = {
+ { "remoteselement", &cfg_type_remoteselement, 0 },
+ { "key", &cfg_type_optional_keyref, 0 },
+ { "tls", &cfg_type_optional_tls, 0 },
+ { NULL, NULL, 0 },
+};
+
+static cfg_type_t cfg_type_namesockaddrkey = {
+ "namesockaddrkey", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, namesockaddrkey_fields
+};
+
+static cfg_type_t cfg_type_bracketed_namesockaddrkeylist = {
+ "bracketed_namesockaddrkeylist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_namesockaddrkey
+};
+
+static cfg_tuplefielddef_t namesockaddrkeylist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_namesockaddrkeylist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_namesockaddrkeylist = {
+ "sockaddrkeylist", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, namesockaddrkeylist_fields
+};
+
+/*%
+ * A list of socket addresses with an optional default port, as used
+ * in the 'listen-on' option. E.g., "{ 10.0.0.1; 1::2 port 69; }"
+ */
+static cfg_tuplefielddef_t portiplist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { "addresses", &cfg_type_bracketed_dscpsockaddrlist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_portiplist = { "portiplist", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, portiplist_fields };
+
+/*%
+ * A list of RR types, used in grant statements.
+ * Note that the old parser allows quotes around the RR type names.
+ */
+static cfg_type_t cfg_type_rrtypelist = {
+ "rrtypelist", cfg_parse_spacelist, cfg_print_spacelist,
+ cfg_doc_terminal, &cfg_rep_list, &cfg_type_astring
+};
+
+static const char *mode_enums[] = { "deny", "grant", NULL };
+static cfg_type_t cfg_type_mode = {
+ "mode", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &mode_enums
+};
+
+static isc_result_t
+parse_matchtype(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "zonesub") == 0)
+ {
+ pctx->flags |= CFG_PCTX_SKIP;
+ }
+ return (cfg_parse_enum(pctx, type, ret));
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_matchname(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ if ((pctx->flags & CFG_PCTX_SKIP) != 0) {
+ pctx->flags &= ~CFG_PCTX_SKIP;
+ CHECK(cfg_parse_void(pctx, NULL, &obj));
+ } else {
+ result = cfg_parse_astring(pctx, type, &obj);
+ }
+
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+static void
+doc_matchname(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_print_cstr(pctx, "[ ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static const char *matchtype_enums[] = { "6to4-self",
+ "external",
+ "krb5-self",
+ "krb5-selfsub",
+ "krb5-subdomain",
+ "krb5-subdomain-self-rhs",
+ "ms-self",
+ "ms-selfsub",
+ "ms-subdomain",
+ "ms-subdomain-self-rhs",
+ "name",
+ "self",
+ "selfsub",
+ "selfwild",
+ "subdomain",
+ "tcp-self",
+ "wildcard",
+ "zonesub",
+ NULL };
+
+static cfg_type_t cfg_type_matchtype = { "matchtype", parse_matchtype,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &matchtype_enums };
+
+static cfg_type_t cfg_type_matchname = {
+ "optional_matchname", parse_matchname, cfg_print_ustring,
+ doc_matchname, &cfg_rep_tuple, &cfg_type_ustring
+};
+
+/*%
+ * A grant statement, used in the update policy.
+ */
+static cfg_tuplefielddef_t grant_fields[] = {
+ { "mode", &cfg_type_mode, 0 },
+ { "identity", &cfg_type_astring, 0 }, /* domain name */
+ { "matchtype", &cfg_type_matchtype, 0 },
+ { "name", &cfg_type_matchname, 0 }, /* domain name */
+ { "types", &cfg_type_rrtypelist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_grant = { "grant", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, grant_fields };
+
+static cfg_type_t cfg_type_updatepolicy = {
+ "update_policy", parse_updatepolicy, print_updatepolicy,
+ doc_updatepolicy, &cfg_rep_list, &cfg_type_grant
+};
+
+static isc_result_t
+parse_updatepolicy(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '{')
+ {
+ cfg_ungettoken(pctx);
+ return (cfg_parse_bracketed_list(pctx, type, ret));
+ }
+
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "local") == 0)
+ {
+ cfg_obj_t *obj = NULL;
+ CHECK(cfg_create_obj(pctx, &cfg_type_ustring, &obj));
+ obj->value.string.length = strlen("local");
+ obj->value.string.base =
+ isc_mem_get(pctx->mctx, obj->value.string.length + 1);
+ memmove(obj->value.string.base, "local", 5);
+ obj->value.string.base[5] = '\0';
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_ungettoken(pctx);
+ return (ISC_R_UNEXPECTEDTOKEN);
+
+cleanup:
+ return (result);
+}
+
+static void
+print_updatepolicy(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (cfg_obj_isstring(obj)) {
+ cfg_print_ustring(pctx, obj);
+ } else {
+ cfg_print_bracketed_list(pctx, obj);
+ }
+}
+
+static void
+doc_updatepolicy(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_print_cstr(pctx, "( local | { ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, "; ... } )");
+}
+
+/*%
+ * A view statement.
+ */
+static cfg_tuplefielddef_t view_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_viewopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_view = { "view", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, view_fields };
+
+/*%
+ * A zone statement.
+ */
+static cfg_tuplefielddef_t zone_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_zoneopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_zone = { "zone", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, zone_fields };
+
+/*%
+ * A dnssec-policy statement.
+ */
+static cfg_tuplefielddef_t dnssecpolicy_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "options", &cfg_type_dnssecpolicyopts, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnssecpolicy = {
+ "dnssec-policy", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, dnssecpolicy_fields
+};
+
+/*%
+ * A "category" clause in the "logging" statement.
+ */
+static cfg_tuplefielddef_t category_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "destinations", &cfg_type_destinationlist, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_category = { "category", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, category_fields };
+
+static isc_result_t
+parse_maxduration(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_duration, ret));
+}
+
+static void
+doc_maxduration(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_duration);
+}
+
+/*%
+ * A duration or "unlimited", but not "default".
+ */
+static const char *maxduration_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_maxduration = {
+ "maxduration_no_default", parse_maxduration, cfg_print_ustring,
+ doc_maxduration, &cfg_rep_duration, maxduration_enums
+};
+
+/*%
+ * A dnssec key, as used in the "trusted-keys" statement.
+ */
+static cfg_tuplefielddef_t dnsseckey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "anchortype", &cfg_type_void, 0 },
+ { "rdata1", &cfg_type_uint32, 0 },
+ { "rdata2", &cfg_type_uint32, 0 },
+ { "rdata3", &cfg_type_uint32, 0 },
+ { "data", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_dnsseckey = { "dnsseckey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dnsseckey_fields };
+
+/*%
+ * Optional enums.
+ *
+ */
+static isc_result_t
+parse_optional_enum(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_void, ret));
+}
+
+static void
+doc_optional_enum(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ ");
+ cfg_doc_enum(pctx, type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+/*%
+ * A key initialization specifier, as used in the
+ * "trust-anchors" (or synonymous "managed-keys") statement.
+ */
+static const char *anchortype_enums[] = { "static-key", "initial-key",
+ "static-ds", "initial-ds", NULL };
+static cfg_type_t cfg_type_anchortype = { "anchortype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, anchortype_enums };
+static cfg_tuplefielddef_t managedkey_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "anchortype", &cfg_type_anchortype, 0 },
+ { "rdata1", &cfg_type_uint32, 0 },
+ { "rdata2", &cfg_type_uint32, 0 },
+ { "rdata3", &cfg_type_uint32, 0 },
+ { "data", &cfg_type_qstring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_managedkey = { "managedkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, managedkey_fields };
+
+/*%
+ * DNSSEC key roles.
+ */
+static const char *dnsseckeyrole_enums[] = { "csk", "ksk", "zsk", NULL };
+static cfg_type_t cfg_type_dnsseckeyrole = {
+ "dnssec-key-role", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &dnsseckeyrole_enums
+};
+
+/*%
+ * DNSSEC key storage types.
+ */
+static const char *dnsseckeystore_enums[] = { "key-directory", NULL };
+static cfg_type_t cfg_type_dnsseckeystore = {
+ "dnssec-key-storage", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, dnsseckeystore_enums
+};
+
+/*%
+ * A dnssec key, as used in the "keys" statement in a "dnssec-policy".
+ */
+static keyword_type_t algorithm_kw = { "algorithm", &cfg_type_ustring };
+static cfg_type_t cfg_type_algorithm = { "algorithm", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &algorithm_kw };
+
+static keyword_type_t lifetime_kw = { "lifetime",
+ &cfg_type_duration_or_unlimited };
+static cfg_type_t cfg_type_lifetime = { "lifetime", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_duration, &lifetime_kw };
+
+static cfg_tuplefielddef_t kaspkey_fields[] = {
+ { "role", &cfg_type_dnsseckeyrole, 0 },
+ { "keystore-type", &cfg_type_dnsseckeystore, 0 },
+ { "lifetime", &cfg_type_lifetime, 0 },
+ { "algorithm", &cfg_type_algorithm, 0 },
+ { "length", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_kaspkey = { "kaspkey", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, kaspkey_fields };
+
+/*%
+ * NSEC3 parameters.
+ */
+static keyword_type_t nsec3iter_kw = { "iterations", &cfg_type_uint32 };
+static cfg_type_t cfg_type_nsec3iter = {
+ "iterations", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &nsec3iter_kw
+};
+
+static keyword_type_t nsec3optout_kw = { "optout", &cfg_type_boolean };
+static cfg_type_t cfg_type_nsec3optout = {
+ "optout", parse_optional_keyvalue,
+ print_keyvalue, doc_optional_keyvalue,
+ &cfg_rep_boolean, &nsec3optout_kw
+};
+
+static keyword_type_t nsec3salt_kw = { "salt-length", &cfg_type_uint32 };
+static cfg_type_t cfg_type_nsec3salt = {
+ "salt-length", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &nsec3salt_kw
+};
+
+static cfg_tuplefielddef_t nsec3param_fields[] = {
+ { "iterations", &cfg_type_nsec3iter, 0 },
+ { "optout", &cfg_type_nsec3optout, 0 },
+ { "salt-length", &cfg_type_nsec3salt, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nsec3 = { "nsec3param", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, nsec3param_fields };
+
+/*%
+ * Wild class, type, name.
+ */
+static keyword_type_t wild_class_kw = { "class", &cfg_type_ustring };
+
+static cfg_type_t cfg_type_optional_wild_class = {
+ "optional_wild_class", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_class_kw
+};
+
+static keyword_type_t wild_type_kw = { "type", &cfg_type_ustring };
+
+static cfg_type_t cfg_type_optional_wild_type = {
+ "optional_wild_type", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_type_kw
+};
+
+static keyword_type_t wild_name_kw = { "name", &cfg_type_qstring };
+
+static cfg_type_t cfg_type_optional_wild_name = {
+ "optional_wild_name", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &wild_name_kw
+};
+
+/*%
+ * An rrset ordering element.
+ */
+static cfg_tuplefielddef_t rrsetorderingelement_fields[] = {
+ { "class", &cfg_type_optional_wild_class, 0 },
+ { "type", &cfg_type_optional_wild_type, 0 },
+ { "name", &cfg_type_optional_wild_name, 0 },
+ { "order", &cfg_type_ustring, 0 }, /* must be literal "order" */
+ { "ordering", &cfg_type_ustring, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rrsetorderingelement = {
+ "rrsetorderingelement", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, rrsetorderingelement_fields
+};
+
+/*%
+ * A global or view "check-names" option. Note that the zone
+ * "check-names" option has a different syntax.
+ */
+
+static const char *checktype_enums[] = { "primary", "master", "secondary",
+ "slave", "response", NULL };
+static cfg_type_t cfg_type_checktype = { "checktype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &checktype_enums };
+
+static const char *checkmode_enums[] = { "fail", "warn", "ignore", NULL };
+static cfg_type_t cfg_type_checkmode = { "checkmode", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &checkmode_enums };
+
+static const char *warn_enums[] = { "warn", "ignore", NULL };
+static cfg_type_t cfg_type_warn = {
+ "warn", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &warn_enums
+};
+
+static cfg_tuplefielddef_t checknames_fields[] = {
+ { "type", &cfg_type_checktype, 0 },
+ { "mode", &cfg_type_checkmode, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_checknames = { "checknames", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, checknames_fields };
+
+static cfg_type_t cfg_type_bracketed_dscpsockaddrlist = {
+ "bracketed_sockaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_sockaddrdscp
+};
+
+static cfg_type_t cfg_type_bracketed_netaddrlist = { "bracketed_netaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_netaddr };
+
+static const char *autodnssec_enums[] = { "allow", "maintain", "off", NULL };
+static cfg_type_t cfg_type_autodnssec = {
+ "autodnssec", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &autodnssec_enums
+};
+
+static const char *dnssecupdatemode_enums[] = { "maintain", "no-resign", NULL };
+static cfg_type_t cfg_type_dnssecupdatemode = {
+ "dnssecupdatemode", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &dnssecupdatemode_enums
+};
+
+static const char *updatemethods_enums[] = { "date", "increment", "unixtime",
+ NULL };
+static cfg_type_t cfg_type_updatemethod = {
+ "updatemethod", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &updatemethods_enums
+};
+
+/*
+ * zone-statistics: full, terse, or none.
+ *
+ * for backward compatibility, we also support boolean values.
+ * yes represents "full", no represents "terse". in the future we
+ * may change no to mean "none".
+ */
+static const char *zonestat_enums[] = { "full", "terse", "none", NULL };
+static isc_result_t
+parse_zonestat(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_zonestat(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_zonestat = { "zonestat", parse_zonestat,
+ cfg_print_ustring, doc_zonestat,
+ &cfg_rep_string, zonestat_enums };
+
+static cfg_type_t cfg_type_rrsetorder = { "rrsetorder",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_rrsetorderingelement };
+
+static keyword_type_t dscp_kw = { "dscp", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_optional_dscp = {
+ "optional_dscp", parse_optional_keyvalue, print_keyvalue,
+ cfg_doc_void, &cfg_rep_uint32, &dscp_kw
+};
+
+static keyword_type_t port_kw = { "port", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_optional_port = {
+ "optional_port", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_uint32, &port_kw
+};
+
+/*% A list of keys, as in the "key" clause of the controls statement. */
+static cfg_type_t cfg_type_keylist = { "keylist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+/*% A list of dnssec keys, as in "trusted-keys". Deprecated. */
+static cfg_type_t cfg_type_trustedkeys = { "trustedkeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_dnsseckey };
+
+/*%
+ * A list of managed trust anchors. Each entry contains a name, a keyword
+ * ("static-key", initial-key", "static-ds" or "initial-ds"), and the
+ * fields associated with either a DNSKEY or a DS record.
+ */
+static cfg_type_t cfg_type_dnsseckeys = { "dnsseckeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_managedkey };
+
+/*%
+ * A list of key entries, used in a DNSSEC Key and Signing Policy.
+ */
+static cfg_type_t cfg_type_kaspkeys = { "kaspkeys",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_kaspkey };
+
+static const char *forwardtype_enums[] = { "first", "only", NULL };
+static cfg_type_t cfg_type_forwardtype = {
+ "forwardtype", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &forwardtype_enums
+};
+
+static const char *zonetype_enums[] = {
+ "primary", "master", "secondary", "slave",
+ "mirror", "delegation-only", "forward", "hint",
+ "redirect", "static-stub", "stub", NULL
+};
+static cfg_type_t cfg_type_zonetype = { "zonetype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &zonetype_enums };
+
+static const char *loglevel_enums[] = { "critical", "error", "warning",
+ "notice", "info", "dynamic",
+ NULL };
+static cfg_type_t cfg_type_loglevel = { "loglevel", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &loglevel_enums };
+
+static const char *transferformat_enums[] = { "many-answers", "one-answer",
+ NULL };
+static cfg_type_t cfg_type_transferformat = {
+ "transferformat", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &transferformat_enums
+};
+
+/*%
+ * The special keyword "none", as used in the pid-file option.
+ */
+
+static void
+print_none(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "none");
+}
+
+static cfg_type_t cfg_type_none = { "none", NULL, print_none,
+ NULL, &cfg_rep_void, NULL };
+
+/*%
+ * A quoted string or the special keyword "none". Used in the pid-file option.
+ */
+static isc_result_t
+parse_qstringornone(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "none") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_none, ret));
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_qstring(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+doc_qstringornone(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( <quoted_string> | none )");
+}
+
+static cfg_type_t cfg_type_qstringornone = { "qstringornone",
+ parse_qstringornone,
+ NULL,
+ doc_qstringornone,
+ NULL,
+ NULL };
+
+/*%
+ * A boolean ("yes" or "no"), or the special keyword "auto".
+ * Used in the dnssec-validation option.
+ */
+static void
+print_auto(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "auto");
+}
+
+static cfg_type_t cfg_type_auto = { "auto", NULL, print_auto,
+ NULL, &cfg_rep_void, NULL };
+
+static isc_result_t
+parse_boolorauto(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "auto") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_auto, ret));
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_boolean(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+print_boolorauto(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->type->rep == &cfg_rep_void) {
+ cfg_print_cstr(pctx, "auto");
+ } else if (obj->value.boolean) {
+ cfg_print_cstr(pctx, "yes");
+ } else {
+ cfg_print_cstr(pctx, "no");
+ }
+}
+
+static void
+doc_boolorauto(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( yes | no | auto )");
+}
+
+static cfg_type_t cfg_type_boolorauto = {
+ "boolorauto", parse_boolorauto, print_boolorauto, doc_boolorauto, NULL,
+ NULL
+};
+
+/*%
+ * keyword hostname
+ */
+static void
+print_hostname(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ UNUSED(obj);
+ cfg_print_cstr(pctx, "hostname");
+}
+
+static cfg_type_t cfg_type_hostname = { "hostname", NULL,
+ print_hostname, NULL,
+ &cfg_rep_boolean, NULL };
+
+/*%
+ * "server-id" argument.
+ */
+
+static isc_result_t
+parse_serverid(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "none") == 0)
+ {
+ return (cfg_create_obj(pctx, &cfg_type_none, ret));
+ }
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "hostname") == 0)
+ {
+ result = cfg_create_obj(pctx, &cfg_type_hostname, ret);
+ if (result == ISC_R_SUCCESS) {
+ (*ret)->value.boolean = true;
+ }
+ return (result);
+ }
+ cfg_ungettoken(pctx);
+ return (cfg_parse_qstring(pctx, type, ret));
+cleanup:
+ return (result);
+}
+
+static void
+doc_serverid(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( <quoted_string> | none | hostname )");
+}
+
+static cfg_type_t cfg_type_serverid = { "serverid", parse_serverid, NULL,
+ doc_serverid, NULL, NULL };
+
+/*%
+ * Port list.
+ */
+static void
+print_porttuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "range ");
+ cfg_print_tuple(pctx, obj);
+}
+static cfg_tuplefielddef_t porttuple_fields[] = {
+ { "loport", &cfg_type_uint32, 0 },
+ { "hiport", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_porttuple = { "porttuple", cfg_parse_tuple,
+ print_porttuple, cfg_doc_tuple,
+ &cfg_rep_tuple, porttuple_fields };
+
+static isc_result_t
+parse_port(cfg_parser_t *pctx, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ CHECK(cfg_parse_uint32(pctx, NULL, ret));
+ if ((*ret)->value.uint32 > 0xffff) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid port");
+ cfg_obj_destroy(pctx, ret);
+ result = ISC_R_RANGE;
+ }
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_portrange(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(parse_port(pctx, ret));
+ } else {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string ||
+ strcasecmp(TOKEN_STRING(pctx), "range") != 0)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer or 'range'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ CHECK(cfg_create_tuple(pctx, &cfg_type_porttuple, &obj));
+ CHECK(parse_port(pctx, &obj->value.tuple[0]));
+ CHECK(parse_port(pctx, &obj->value.tuple[1]));
+ if (obj->value.tuple[0]->value.uint32 >
+ obj->value.tuple[1]->value.uint32)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "low port '%u' must not be larger "
+ "than high port",
+ obj->value.tuple[0]->value.uint32);
+ result = ISC_R_RANGE;
+ goto cleanup;
+ }
+ *ret = obj;
+ obj = NULL;
+ }
+
+cleanup:
+ if (obj != NULL) {
+ cfg_obj_destroy(pctx, &obj);
+ }
+ return (result);
+}
+
+static cfg_type_t cfg_type_portrange = { "portrange", parse_portrange,
+ NULL, cfg_doc_terminal,
+ NULL, NULL };
+
+static cfg_type_t cfg_type_bracketed_portlist = { "bracketed_sockaddrlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_portrange };
+
+static const char *cookiealg_enums[] = { "aes", "siphash24", NULL };
+static cfg_type_t cfg_type_cookiealg = { "cookiealg", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &cookiealg_enums };
+
+/*%
+ * fetch-quota-params
+ */
+
+static cfg_tuplefielddef_t fetchquota_fields[] = {
+ { "frequency", &cfg_type_uint32, 0 },
+ { "low", &cfg_type_fixedpoint, 0 },
+ { "high", &cfg_type_fixedpoint, 0 },
+ { "discount", &cfg_type_fixedpoint, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_fetchquota = { "fetchquota", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, fetchquota_fields };
+
+/*%
+ * fetches-per-server or fetches-per-zone
+ */
+
+static const char *response_enums[] = { "drop", "fail", NULL };
+
+static cfg_type_t cfg_type_responsetype = {
+ "responsetype", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, response_enums
+};
+
+static cfg_tuplefielddef_t fetchesper_fields[] = {
+ { "fetches", &cfg_type_uint32, 0 },
+ { "response", &cfg_type_responsetype, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_fetchesper = { "fetchesper", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, fetchesper_fields };
+
+/*%
+ * Clauses that can be found within the top level of the named.conf
+ * file only.
+ */
+static cfg_clausedef_t namedconf_clauses[] = {
+ { "acl", &cfg_type_acl, CFG_CLAUSEFLAG_MULTI },
+ { "controls", &cfg_type_controls, CFG_CLAUSEFLAG_MULTI },
+ { "dnssec-policy", &cfg_type_dnssecpolicy, CFG_CLAUSEFLAG_MULTI },
+#if HAVE_LIBNGHTTP2
+ { "http", &cfg_type_http_description, CFG_CLAUSEFLAG_MULTI },
+#else
+ { "http", &cfg_type_http_description,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { "logging", &cfg_type_logging, 0 },
+ { "lwres", NULL, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT },
+ { "masters", &cfg_type_remoteservers,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NODOC },
+ { "options", &cfg_type_options, 0 },
+ { "parental-agents", &cfg_type_remoteservers, CFG_CLAUSEFLAG_MULTI },
+ { "primaries", &cfg_type_remoteservers, CFG_CLAUSEFLAG_MULTI },
+ { "statistics-channels", &cfg_type_statschannels,
+ CFG_CLAUSEFLAG_MULTI },
+ { "tls", &cfg_type_tlsconf, CFG_CLAUSEFLAG_MULTI },
+ { "view", &cfg_type_view, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can occur at the top level or in the view
+ * statement, but not in the options block.
+ */
+static cfg_clausedef_t namedconf_or_view_clauses[] = {
+ { "dlz", &cfg_type_dlz, CFG_CLAUSEFLAG_MULTI },
+ { "dyndb", &cfg_type_dyndb, CFG_CLAUSEFLAG_MULTI },
+ { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
+ { "managed-keys", &cfg_type_dnsseckeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "plugin", &cfg_type_plugin, CFG_CLAUSEFLAG_MULTI },
+ { "server", &cfg_type_server, CFG_CLAUSEFLAG_MULTI },
+ { "trust-anchors", &cfg_type_dnsseckeys, CFG_CLAUSEFLAG_MULTI },
+ { "trusted-keys", &cfg_type_trustedkeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "zone", &cfg_type_zone, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NODOC },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can occur in the bind.keys file.
+ */
+static cfg_clausedef_t bindkeys_clauses[] = {
+ { "managed-keys", &cfg_type_dnsseckeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "trust-anchors", &cfg_type_dnsseckeys, CFG_CLAUSEFLAG_MULTI },
+ { "trusted-keys", &cfg_type_trustedkeys,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { NULL, NULL, 0 }
+};
+
+static const char *fstrm_model_enums[] = { "mpsc", "spsc", NULL };
+static cfg_type_t cfg_type_fstrm_model = {
+ "model", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &fstrm_model_enums
+};
+
+/*%
+ * Clauses that can be found within the 'options' statement.
+ */
+static cfg_clausedef_t options_clauses[] = {
+ { "answer-cookie", &cfg_type_boolean, 0 },
+ { "automatic-interface-scan", &cfg_type_boolean, 0 },
+ { "avoid-v4-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "avoid-v6-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "bindkeys-file", &cfg_type_qstring, 0 },
+ { "blackhole", &cfg_type_bracketed_aml, 0 },
+ { "cookie-algorithm", &cfg_type_cookiealg, 0 },
+ { "cookie-secret", &cfg_type_sstring, CFG_CLAUSEFLAG_MULTI },
+ { "coresize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "datasize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "deallocate-on-exit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "directory", &cfg_type_qstring, CFG_CLAUSEFLAG_CALLBACK },
+#ifdef HAVE_DNSTAP
+ { "dnstap-output", &cfg_type_dnstapoutput, 0 },
+ { "dnstap-identity", &cfg_type_serverid, 0 },
+ { "dnstap-version", &cfg_type_qstringornone, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "dnstap-output", &cfg_type_dnstapoutput,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnstap-identity", &cfg_type_serverid, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnstap-version", &cfg_type_qstringornone,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef HAVE_DNSTAP */
+ { "dscp", &cfg_type_uint32, CFG_CLAUSEFLAG_OBSOLETE },
+ { "dump-file", &cfg_type_qstring, 0 },
+ { "fake-iquery", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "files", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "flush-zones-on-shutdown", &cfg_type_boolean, 0 },
+#ifdef HAVE_DNSTAP
+ { "fstrm-set-buffer-hint", &cfg_type_uint32, 0 },
+ { "fstrm-set-flush-timeout", &cfg_type_uint32, 0 },
+ { "fstrm-set-input-queue-size", &cfg_type_uint32, 0 },
+ { "fstrm-set-output-notify-threshold", &cfg_type_uint32, 0 },
+ { "fstrm-set-output-queue-model", &cfg_type_fstrm_model, 0 },
+ { "fstrm-set-output-queue-size", &cfg_type_uint32, 0 },
+ { "fstrm-set-reopen-interval", &cfg_type_duration, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "fstrm-set-buffer-hint", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-flush-timeout", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-input-queue-size", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-notify-threshold", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-queue-model", &cfg_type_fstrm_model,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-output-queue-size", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "fstrm-set-reopen-interval", &cfg_type_duration,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_DNSTAP */
+#if defined(HAVE_GEOIP2)
+ { "geoip-directory", &cfg_type_qstringornone, 0 },
+#else /* if defined(HAVE_GEOIP2) */
+ { "geoip-directory", &cfg_type_qstringornone,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_GEOIP2 */
+ { "geoip-use-ecs", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "has-old-clients", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "heartbeat-interval", &cfg_type_uint32, CFG_CLAUSEFLAG_DEPRECATED },
+ { "host-statistics", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "host-statistics-max", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "hostname", &cfg_type_qstringornone, 0 },
+ { "interface-interval", &cfg_type_duration, 0 },
+ { "keep-response-order", &cfg_type_bracketed_aml, 0 },
+ { "listen-on", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
+ { "listen-on-v6", &cfg_type_listenon, CFG_CLAUSEFLAG_MULTI },
+ { "lock-file", &cfg_type_qstringornone, 0 },
+ { "managed-keys-directory", &cfg_type_qstring, 0 },
+ { "match-mapped-addresses", &cfg_type_boolean, 0 },
+ { "max-rsa-exponent-size", &cfg_type_uint32, 0 },
+ { "memstatistics", &cfg_type_boolean, 0 },
+ { "memstatistics-file", &cfg_type_qstring, 0 },
+ { "multiple-cnames", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "named-xfer", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "notify-rate", &cfg_type_uint32, 0 },
+ { "pid-file", &cfg_type_qstringornone, 0 },
+ { "port", &cfg_type_uint32, 0 },
+ { "tls-port", &cfg_type_uint32, 0 },
+#if HAVE_LIBNGHTTP2
+ { "http-port", &cfg_type_uint32, 0 },
+ { "http-listener-clients", &cfg_type_uint32, 0 },
+ { "http-streams-per-connection", &cfg_type_uint32, 0 },
+ { "https-port", &cfg_type_uint32, 0 },
+#else
+ { "http-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "http-listener-clients", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "http-streams-per-connection", &cfg_type_uint32,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "https-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif
+ { "querylog", &cfg_type_boolean, 0 },
+ { "random-device", &cfg_type_qstringornone, CFG_CLAUSEFLAG_OBSOLETE },
+ { "recursing-file", &cfg_type_qstring, 0 },
+ { "recursive-clients", &cfg_type_uint32, 0 },
+ { "reuseport", &cfg_type_boolean, 0 },
+ { "reserved-sockets", &cfg_type_uint32, CFG_CLAUSEFLAG_DEPRECATED },
+ { "secroots-file", &cfg_type_qstring, 0 },
+ { "serial-queries", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "serial-query-rate", &cfg_type_uint32, 0 },
+ { "server-id", &cfg_type_serverid, 0 },
+ { "session-keyalg", &cfg_type_astring, 0 },
+ { "session-keyfile", &cfg_type_qstringornone, 0 },
+ { "session-keyname", &cfg_type_astring, 0 },
+ { "sit-secret", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "stacksize", &cfg_type_size, CFG_CLAUSEFLAG_DEPRECATED },
+ { "startup-notify-rate", &cfg_type_uint32, 0 },
+ { "statistics-file", &cfg_type_qstring, 0 },
+ { "statistics-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "tcp-advertised-timeout", &cfg_type_uint32, 0 },
+ { "tcp-clients", &cfg_type_uint32, 0 },
+ { "tcp-idle-timeout", &cfg_type_uint32, 0 },
+ { "tcp-initial-timeout", &cfg_type_uint32, 0 },
+ { "tcp-keepalive-timeout", &cfg_type_uint32, 0 },
+ { "tcp-listen-queue", &cfg_type_uint32, 0 },
+ { "tcp-receive-buffer", &cfg_type_uint32, 0 },
+ { "tcp-send-buffer", &cfg_type_uint32, 0 },
+ { "tkey-dhkey", &cfg_type_tkey_dhkey, CFG_CLAUSEFLAG_DEPRECATED },
+ { "tkey-domain", &cfg_type_qstring, 0 },
+ { "tkey-gssapi-credential", &cfg_type_qstring, 0 },
+ { "tkey-gssapi-keytab", &cfg_type_qstring, 0 },
+ { "transfer-message-size", &cfg_type_uint32, 0 },
+ { "transfers-in", &cfg_type_uint32, 0 },
+ { "transfers-out", &cfg_type_uint32, 0 },
+ { "transfers-per-ns", &cfg_type_uint32, 0 },
+ { "treat-cr-as-space", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "udp-receive-buffer", &cfg_type_uint32, 0 },
+ { "udp-send-buffer", &cfg_type_uint32, 0 },
+ { "update-quota", &cfg_type_uint32, 0 },
+ { "use-id-pool", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "use-ixfr", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "use-v4-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "use-v6-udp-ports", &cfg_type_bracketed_portlist,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "version", &cfg_type_qstringornone, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_namelist = { "namelist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static keyword_type_t exclude_kw = { "exclude", &cfg_type_namelist };
+
+static cfg_type_t cfg_type_optional_exclude = {
+ "optional_exclude", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &exclude_kw
+};
+
+static keyword_type_t exceptionnames_kw = { "except-from", &cfg_type_namelist };
+
+static cfg_type_t cfg_type_optional_exceptionnames = {
+ "optional_allow", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &exceptionnames_kw
+};
+
+static cfg_tuplefielddef_t denyaddresses_fields[] = {
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { "except-from", &cfg_type_optional_exceptionnames, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_denyaddresses = {
+ "denyaddresses", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, denyaddresses_fields
+};
+
+static cfg_tuplefielddef_t denyaliases_fields[] = {
+ { "name", &cfg_type_namelist, 0 },
+ { "except-from", &cfg_type_optional_exceptionnames, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_denyaliases = {
+ "denyaliases", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, denyaliases_fields
+};
+
+static cfg_type_t cfg_type_algorithmlist = { "algorithmlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_tuplefielddef_t disablealgorithm_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "algorithms", &cfg_type_algorithmlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_disablealgorithm = {
+ "disablealgorithm", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, disablealgorithm_fields
+};
+
+static cfg_type_t cfg_type_dsdigestlist = { "dsdigestlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_tuplefielddef_t disabledsdigest_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "digests", &cfg_type_dsdigestlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_disabledsdigest = {
+ "disabledsdigest", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, disabledsdigest_fields
+};
+
+static cfg_tuplefielddef_t mustbesecure_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "value", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_mustbesecure = {
+ "mustbesecure", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, mustbesecure_fields
+};
+
+static const char *masterformat_enums[] = { "raw", "text", NULL };
+static cfg_type_t cfg_type_masterformat = {
+ "masterformat", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &masterformat_enums
+};
+
+static const char *masterstyle_enums[] = { "full", "relative", NULL };
+static cfg_type_t cfg_type_masterstyle = {
+ "masterstyle", cfg_parse_enum, cfg_print_ustring,
+ cfg_doc_enum, &cfg_rep_string, &masterstyle_enums
+};
+
+static keyword_type_t blocksize_kw = { "block-size", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_blocksize = { "blocksize", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_uint32, &blocksize_kw };
+
+static cfg_tuplefielddef_t resppadding_fields[] = {
+ { "acl", &cfg_type_bracketed_aml, 0 },
+ { "block-size", &cfg_type_blocksize, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_resppadding = {
+ "resppadding", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, resppadding_fields
+};
+
+/*%
+ * dnstap {
+ * &lt;message type&gt; [query | response] ;
+ * ...
+ * }
+ *
+ * ... where message type is one of: client, resolver, auth, forwarder,
+ * update, all
+ */
+static const char *dnstap_types[] = { "all", "auth", "client",
+ "forwarder", "resolver", "update",
+ NULL };
+
+static const char *dnstap_modes[] = { "query", "response", NULL };
+
+static cfg_type_t cfg_type_dnstap_type = { "dnstap_type", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, dnstap_types };
+
+static cfg_type_t cfg_type_dnstap_mode = {
+ "dnstap_mode", parse_optional_enum, cfg_print_ustring,
+ doc_optional_enum, &cfg_rep_string, dnstap_modes
+};
+
+static cfg_tuplefielddef_t dnstap_fields[] = {
+ { "type", &cfg_type_dnstap_type, 0 },
+ { "mode", &cfg_type_dnstap_mode, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnstap_entry = { "dnstap_value", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dnstap_fields };
+
+static cfg_type_t cfg_type_dnstap = { "dnstap",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_dnstap_entry };
+
+/*%
+ * dnstap-output
+ */
+static isc_result_t
+parse_dtout(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const cfg_tuplefielddef_t *fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ /* Parse the mandatory "mode" and "path" fields */
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+ CHECK(cfg_parse_obj(pctx, fields[1].type, &obj->value.tuple[1]));
+
+ /* Parse "versions" and "size" fields in any order. */
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "size") == 0 &&
+ obj->value.tuple[2] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "versions") ==
+ 0 &&
+ obj->value.tuple[3] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[3].type,
+ &obj->value.tuple[3]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "suffix") ==
+ 0 &&
+ obj->value.tuple[4] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[4].type,
+ &obj->value.tuple[4]));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "unexpected token");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Create void objects for missing optional values. */
+ if (obj->value.tuple[2] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
+ }
+ if (obj->value.tuple[3] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
+ }
+ if (obj->value.tuple[4] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[4]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_dtout(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_obj(pctx, obj->value.tuple[0]); /* mode */
+ cfg_print_obj(pctx, obj->value.tuple[1]); /* file */
+ if (obj->value.tuple[2]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " size ");
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ }
+ if (obj->value.tuple[3]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " versions ");
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+ }
+ if (obj->value.tuple[4]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[4]);
+ }
+}
+
+static void
+doc_dtout(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( file | unix ) <quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ size ( unlimited | <size> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
+}
+
+static const char *dtoutmode_enums[] = { "file", "unix", NULL };
+static cfg_type_t cfg_type_dtmode = { "dtmode", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &dtoutmode_enums };
+
+static cfg_tuplefielddef_t dtout_fields[] = {
+ { "mode", &cfg_type_dtmode, 0 },
+ { "path", &cfg_type_qstring, 0 },
+ { "size", &cfg_type_sizenodefault, 0 },
+ { "versions", &cfg_type_logversions, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dnstapoutput = { "dnstapoutput", parse_dtout,
+ print_dtout, doc_dtout,
+ &cfg_rep_tuple, dtout_fields };
+
+/*%
+ * response-policy {
+ * zone &lt;string&gt; [ policy (given|disabled|passthru|drop|tcp-only|
+ * nxdomain|nodata|cname &lt;domain&gt; ) ]
+ * [ recursive-only yes|no ] [ log yes|no ]
+ * [ max-policy-ttl number ]
+ * [ nsip-enable yes|no ] [ nsdname-enable yes|no ];
+ * } [ recursive-only yes|no ] [ max-policy-ttl number ]
+ * [ min-update-interval number ]
+ * [ break-dnssec yes|no ] [ min-ns-dots number ]
+ * [ qname-wait-recurse yes|no ]
+ * [ nsip-enable yes|no ] [ nsdname-enable yes|no ]
+ * [ dnsrps-enable yes|no ]
+ * [ dnsrps-options { DNSRPS configuration string } ];
+ */
+
+static void
+doc_rpz_policy(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const char *const *p;
+ /*
+ * This is cfg_doc_enum() without the trailing " )".
+ */
+ cfg_print_cstr(pctx, "( ");
+ for (p = type->of; *p != NULL; p++) {
+ cfg_print_cstr(pctx, *p);
+ if (p[1] != NULL) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ }
+}
+
+static void
+doc_rpz_cname(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_terminal(pctx, type);
+ cfg_print_cstr(pctx, " )");
+}
+
+/*
+ * Parse
+ * given|disabled|passthru|drop|tcp-only|nxdomain|nodata|cname <domain>
+ */
+static isc_result_t
+cfg_parse_rpz_policy(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const cfg_tuplefielddef_t *fields;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ fields = type->of;
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+ /*
+ * parse cname domain only after "policy cname"
+ */
+ if (strcasecmp("cname", cfg_obj_asstring(obj->value.tuple[0])) != 0) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ } else {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+/*
+ * Parse a tuple consisting of any kind of required field followed
+ * by 2 or more optional keyvalues that can be in any order.
+ */
+static isc_result_t
+cfg_parse_kv_tuple(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ const cfg_tuplefielddef_t *fields, *f;
+ cfg_obj_t *obj = NULL;
+ int fn;
+ isc_result_t result;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ /*
+ * The zone first field is required and always first.
+ */
+ fields = type->of;
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type != isc_tokentype_string) {
+ break;
+ }
+
+ for (fn = 1, f = &fields[1];; ++fn, ++f) {
+ if (f->name == NULL) {
+ cfg_parser_error(pctx, 0, "unexpected '%s'",
+ TOKEN_STRING(pctx));
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ if (obj->value.tuple[fn] == NULL &&
+ strcasecmp(f->name, TOKEN_STRING(pctx)) == 0)
+ {
+ break;
+ }
+ }
+
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[fn]));
+ }
+
+ for (fn = 1, f = &fields[1]; f->name != NULL; ++fn, ++f) {
+ if (obj->value.tuple[fn] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL,
+ &obj->value.tuple[fn]));
+ }
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+cfg_print_kv_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields, *f;
+ const cfg_obj_t *fieldobj;
+
+ fields = obj->type->of;
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ fieldobj = obj->value.tuple[i];
+ if (fieldobj->type->print == cfg_print_void) {
+ continue;
+ }
+ if (i != 0) {
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, f->name);
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_print_obj(pctx, fieldobj);
+ }
+}
+
+static void
+cfg_doc_kv_tuple(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_tuplefielddef_t *fields, *f;
+
+ fields = type->of;
+ for (f = fields; f->name != NULL; f++) {
+ if ((f->flags & CFG_CLAUSEFLAG_NODOC) != 0) {
+ continue;
+ }
+ if (f != fields) {
+ cfg_print_cstr(pctx, " [ ");
+ cfg_print_cstr(pctx, f->name);
+ if (f->type->doc != cfg_doc_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ }
+ cfg_doc_obj(pctx, f->type);
+ if (f != fields) {
+ cfg_print_cstr(pctx, " ]");
+ }
+ }
+}
+
+static keyword_type_t zone_kw = { "zone", &cfg_type_astring };
+static cfg_type_t cfg_type_rpz_zone = { "zone", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &zone_kw };
+/*
+ * "no-op" is an obsolete equivalent of "passthru".
+ */
+static const char *rpz_policies[] = { "cname", "disabled", "drop",
+ "given", "no-op", "nodata",
+ "nxdomain", "passthru", "tcp-only",
+ NULL };
+static cfg_type_t cfg_type_rpz_policy_name = {
+ "policy name", cfg_parse_enum, cfg_print_ustring,
+ doc_rpz_policy, &cfg_rep_string, &rpz_policies
+};
+static cfg_type_t cfg_type_rpz_cname = {
+ "quoted_string", cfg_parse_astring, NULL,
+ doc_rpz_cname, &cfg_rep_string, NULL
+};
+static cfg_tuplefielddef_t rpz_policy_fields[] = {
+ { "policy name", &cfg_type_rpz_policy_name, 0 },
+ { "cname", &cfg_type_rpz_cname, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz_policy = { "policy tuple", cfg_parse_rpz_policy,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, rpz_policy_fields };
+static cfg_tuplefielddef_t rpz_zone_fields[] = {
+ { "zone name", &cfg_type_rpz_zone, 0 },
+ { "add-soa", &cfg_type_boolean, 0 },
+ { "log", &cfg_type_boolean, 0 },
+ { "max-policy-ttl", &cfg_type_duration, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { "policy", &cfg_type_rpz_policy, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "nsip-enable", &cfg_type_boolean, 0 },
+ { "nsdname-enable", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz_tuple = { "rpz tuple", cfg_parse_kv_tuple,
+ cfg_print_kv_tuple, cfg_doc_kv_tuple,
+ &cfg_rep_tuple, rpz_zone_fields };
+static cfg_type_t cfg_type_rpz_list = { "zone list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_rpz_tuple };
+static cfg_tuplefielddef_t rpz_fields[] = {
+ { "zone list", &cfg_type_rpz_list, 0 },
+ { "add-soa", &cfg_type_boolean, 0 },
+ { "break-dnssec", &cfg_type_boolean, 0 },
+ { "max-policy-ttl", &cfg_type_duration, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { "min-ns-dots", &cfg_type_uint32, 0 },
+ { "nsip-wait-recurse", &cfg_type_boolean, 0 },
+ { "nsdname-wait-recurse", &cfg_type_boolean, 0 },
+ { "qname-wait-recurse", &cfg_type_boolean, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "nsip-enable", &cfg_type_boolean, 0 },
+ { "nsdname-enable", &cfg_type_boolean, 0 },
+#ifdef USE_DNSRPS
+ { "dnsrps-enable", &cfg_type_boolean, 0 },
+ { "dnsrps-options", &cfg_type_bracketed_text, 0 },
+#else /* ifdef USE_DNSRPS */
+ { "dnsrps-enable", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnsrps-options", &cfg_type_bracketed_text,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef USE_DNSRPS */
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_rpz = { "rpz",
+ cfg_parse_kv_tuple,
+ cfg_print_kv_tuple,
+ cfg_doc_kv_tuple,
+ &cfg_rep_tuple,
+ rpz_fields };
+
+/*
+ * Catalog zones
+ */
+static cfg_type_t cfg_type_catz_zone = { "zone", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_string, &zone_kw };
+
+static cfg_tuplefielddef_t catz_zone_fields[] = {
+ { "zone name", &cfg_type_catz_zone, 0 },
+ { "default-masters", &cfg_type_namesockaddrkeylist,
+ CFG_CLAUSEFLAG_NODOC },
+ { "default-primaries", &cfg_type_namesockaddrkeylist, 0 },
+ { "zone-directory", &cfg_type_qstring, 0 },
+ { "in-memory", &cfg_type_boolean, 0 },
+ { "min-update-interval", &cfg_type_duration, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_catz_tuple = {
+ "catz tuple", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, catz_zone_fields
+};
+static cfg_type_t cfg_type_catz_list = { "zone list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_catz_tuple };
+static cfg_tuplefielddef_t catz_fields[] = {
+ { "zone list", &cfg_type_catz_list, 0 }, { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_catz = {
+ "catz", cfg_parse_kv_tuple, cfg_print_kv_tuple,
+ cfg_doc_kv_tuple, &cfg_rep_tuple, catz_fields
+};
+
+/*
+ * rate-limit
+ */
+static cfg_clausedef_t rrl_clauses[] = {
+ { "all-per-second", &cfg_type_uint32, 0 },
+ { "errors-per-second", &cfg_type_uint32, 0 },
+ { "exempt-clients", &cfg_type_bracketed_aml, 0 },
+ { "ipv4-prefix-length", &cfg_type_uint32, 0 },
+ { "ipv6-prefix-length", &cfg_type_uint32, 0 },
+ { "log-only", &cfg_type_boolean, 0 },
+ { "max-table-size", &cfg_type_uint32, 0 },
+ { "min-table-size", &cfg_type_uint32, 0 },
+ { "nodata-per-second", &cfg_type_uint32, 0 },
+ { "nxdomains-per-second", &cfg_type_uint32, 0 },
+ { "qps-scale", &cfg_type_uint32, 0 },
+ { "referrals-per-second", &cfg_type_uint32, 0 },
+ { "responses-per-second", &cfg_type_uint32, 0 },
+ { "slip", &cfg_type_uint32, 0 },
+ { "window", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rrl_clausesets[] = { rrl_clauses, NULL };
+
+static cfg_type_t cfg_type_rrl = { "rate-limit", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rrl_clausesets };
+
+static isc_result_t
+parse_optional_uint32(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_uint32, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_uint32(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <integer> ]");
+}
+
+static cfg_type_t cfg_type_optional_uint32 = { "optional_uint32",
+ parse_optional_uint32,
+ NULL,
+ doc_optional_uint32,
+ NULL,
+ NULL };
+
+static cfg_tuplefielddef_t prefetch_fields[] = {
+ { "trigger", &cfg_type_uint32, 0 },
+ { "eligible", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_prefetch = { "prefetch", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, prefetch_fields };
+/*
+ * DNS64.
+ */
+static cfg_clausedef_t dns64_clauses[] = {
+ { "break-dnssec", &cfg_type_boolean, 0 },
+ { "clients", &cfg_type_bracketed_aml, 0 },
+ { "exclude", &cfg_type_bracketed_aml, 0 },
+ { "mapped", &cfg_type_bracketed_aml, 0 },
+ { "recursive-only", &cfg_type_boolean, 0 },
+ { "suffix", &cfg_type_netaddr6, 0 },
+ { NULL, NULL, 0 },
+};
+
+static cfg_clausedef_t *dns64_clausesets[] = { dns64_clauses, NULL };
+
+static cfg_type_t cfg_type_dns64 = { "dns64", cfg_parse_netprefix_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, dns64_clausesets };
+
+static const char *staleanswerclienttimeout_enums[] = { "disabled", "off",
+ NULL };
+static isc_result_t
+parse_staleanswerclienttimeout(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
+}
+
+static void
+doc_staleanswerclienttimeout(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_uint32);
+}
+
+static cfg_type_t cfg_type_staleanswerclienttimeout = {
+ "staleanswerclienttimeout",
+ parse_staleanswerclienttimeout,
+ cfg_print_ustring,
+ doc_staleanswerclienttimeout,
+ &cfg_rep_string,
+ staleanswerclienttimeout_enums
+};
+
+/*%
+ * Clauses that can be found within the 'view' statement,
+ * with defaults in the 'options' statement.
+ */
+
+static cfg_clausedef_t view_clauses[] = {
+ { "acache-cleaning-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "acache-enable", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "additional-from-auth", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "additional-from-cache", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "allow-new-zones", &cfg_type_boolean, 0 },
+ { "allow-query-cache", &cfg_type_bracketed_aml, 0 },
+ { "allow-query-cache-on", &cfg_type_bracketed_aml, 0 },
+ { "allow-recursion", &cfg_type_bracketed_aml, 0 },
+ { "allow-recursion-on", &cfg_type_bracketed_aml, 0 },
+ { "allow-v6-synthesis", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "attach-cache", &cfg_type_astring, 0 },
+ { "auth-nxdomain", &cfg_type_boolean, 0 },
+ { "cache-file", &cfg_type_qstring, CFG_CLAUSEFLAG_ANCIENT },
+ { "catalog-zones", &cfg_type_catz, 0 },
+ { "check-names", &cfg_type_checknames, CFG_CLAUSEFLAG_MULTI },
+ { "cleaning-interval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "clients-per-query", &cfg_type_uint32, 0 },
+ { "deny-answer-addresses", &cfg_type_denyaddresses, 0 },
+ { "deny-answer-aliases", &cfg_type_denyaliases, 0 },
+ { "disable-algorithms", &cfg_type_disablealgorithm,
+ CFG_CLAUSEFLAG_MULTI },
+ { "disable-ds-digests", &cfg_type_disabledsdigest,
+ CFG_CLAUSEFLAG_MULTI },
+ { "disable-empty-zone", &cfg_type_astring, CFG_CLAUSEFLAG_MULTI },
+ { "dns64", &cfg_type_dns64, CFG_CLAUSEFLAG_MULTI },
+ { "dns64-contact", &cfg_type_astring, 0 },
+ { "dns64-server", &cfg_type_astring, 0 },
+#ifdef USE_DNSRPS
+ { "dnsrps-enable", &cfg_type_boolean, 0 },
+ { "dnsrps-options", &cfg_type_bracketed_text, 0 },
+#else /* ifdef USE_DNSRPS */
+ { "dnsrps-enable", &cfg_type_boolean, CFG_CLAUSEFLAG_NOTCONFIGURED },
+ { "dnsrps-options", &cfg_type_bracketed_text,
+ CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef USE_DNSRPS */
+ { "dnssec-accept-expired", &cfg_type_boolean, 0 },
+ { "dnssec-enable", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "dnssec-lookaside", NULL,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT },
+ { "dnssec-must-be-secure", &cfg_type_mustbesecure,
+ CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_DEPRECATED },
+ { "dnssec-validation", &cfg_type_boolorauto, 0 },
+#ifdef HAVE_DNSTAP
+ { "dnstap", &cfg_type_dnstap, 0 },
+#else /* ifdef HAVE_DNSTAP */
+ { "dnstap", &cfg_type_dnstap, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* HAVE_DNSTAP */
+ { "dual-stack-servers", &cfg_type_nameportiplist, 0 },
+ { "edns-udp-size", &cfg_type_uint32, 0 },
+ { "empty-contact", &cfg_type_astring, 0 },
+ { "empty-server", &cfg_type_astring, 0 },
+ { "empty-zones-enable", &cfg_type_boolean, 0 },
+ { "fetch-glue", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "fetch-quota-params", &cfg_type_fetchquota, 0 },
+ { "fetches-per-server", &cfg_type_fetchesper, 0 },
+ { "fetches-per-zone", &cfg_type_fetchesper, 0 },
+ { "filter-aaaa", &cfg_type_bracketed_aml, CFG_CLAUSEFLAG_ANCIENT },
+ { "filter-aaaa-on-v4", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "filter-aaaa-on-v6", &cfg_type_boolean, CFG_CLAUSEFLAG_ANCIENT },
+ { "glue-cache", &cfg_type_boolean, CFG_CLAUSEFLAG_DEPRECATED },
+ { "ipv4only-enable", &cfg_type_boolean, 0 },
+ { "ipv4only-contact", &cfg_type_astring, 0 },
+ { "ipv4only-server", &cfg_type_astring, 0 },
+ { "ixfr-from-differences", &cfg_type_ixfrdifftype, 0 },
+ { "lame-ttl", &cfg_type_duration, 0 },
+#ifdef HAVE_LMDB
+ { "lmdb-mapsize", &cfg_type_sizeval, 0 },
+#else /* ifdef HAVE_LMDB */
+ { "lmdb-mapsize", &cfg_type_sizeval, CFG_CLAUSEFLAG_NOTCONFIGURED },
+#endif /* ifdef HAVE_LMDB */
+ { "max-acache-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "max-cache-size", &cfg_type_sizeorpercent, 0 },
+ { "max-cache-ttl", &cfg_type_duration, 0 },
+ { "max-clients-per-query", &cfg_type_uint32, 0 },
+ { "max-ncache-ttl", &cfg_type_duration, 0 },
+ { "max-recursion-depth", &cfg_type_uint32, 0 },
+ { "max-recursion-queries", &cfg_type_uint32, 0 },
+ { "max-stale-ttl", &cfg_type_duration, 0 },
+ { "max-udp-size", &cfg_type_uint32, 0 },
+ { "message-compression", &cfg_type_boolean, 0 },
+ { "min-cache-ttl", &cfg_type_duration, 0 },
+ { "min-ncache-ttl", &cfg_type_duration, 0 },
+ { "min-roots", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "minimal-any", &cfg_type_boolean, 0 },
+ { "minimal-responses", &cfg_type_minimal, 0 },
+ { "new-zones-directory", &cfg_type_qstring, 0 },
+ { "no-case-compress", &cfg_type_bracketed_aml, 0 },
+ { "nocookie-udp-size", &cfg_type_uint32, 0 },
+ { "nosit-udp-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "nta-lifetime", &cfg_type_duration, 0 },
+ { "nta-recheck", &cfg_type_duration, 0 },
+ { "nxdomain-redirect", &cfg_type_astring, 0 },
+ { "preferred-glue", &cfg_type_astring, 0 },
+ { "prefetch", &cfg_type_prefetch, 0 },
+ { "provide-ixfr", &cfg_type_boolean, 0 },
+ { "qname-minimization", &cfg_type_qminmethod, 0 },
+ /*
+ * Note that the query-source option syntax is different
+ * from the other -source options.
+ */
+ { "query-source", &cfg_type_querysource4, 0 },
+ { "query-source-v6", &cfg_type_querysource6, 0 },
+ { "queryport-pool-ports", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "queryport-pool-updateinterval", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "rate-limit", &cfg_type_rrl, 0 },
+ { "recursion", &cfg_type_boolean, 0 },
+ { "request-nsid", &cfg_type_boolean, 0 },
+ { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "require-server-cookie", &cfg_type_boolean, 0 },
+ { "resolver-nonbackoff-tries", &cfg_type_uint32, 0 },
+ { "resolver-query-timeout", &cfg_type_uint32, 0 },
+ { "resolver-retry-interval", &cfg_type_uint32, 0 },
+ { "response-padding", &cfg_type_resppadding, 0 },
+ { "response-policy", &cfg_type_rpz, 0 },
+ { "rfc2308-type1", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "root-delegation-only", &cfg_type_optional_exclude,
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "root-key-sentinel", &cfg_type_boolean, 0 },
+ { "rrset-order", &cfg_type_rrsetorder, 0 },
+ { "send-cookie", &cfg_type_boolean, 0 },
+ { "servfail-ttl", &cfg_type_duration, 0 },
+ { "sortlist", &cfg_type_bracketed_aml, 0 },
+ { "stale-answer-enable", &cfg_type_boolean, 0 },
+ { "stale-answer-client-timeout", &cfg_type_staleanswerclienttimeout,
+ 0 },
+ { "stale-answer-ttl", &cfg_type_duration, 0 },
+ { "stale-cache-enable", &cfg_type_boolean, 0 },
+ { "stale-refresh-time", &cfg_type_duration, 0 },
+ { "suppress-initial-notify", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "synth-from-dnssec", &cfg_type_boolean, 0 },
+ { "topology", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "transfer-format", &cfg_type_transferformat, 0 },
+ { "trust-anchor-telemetry", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_EXPERIMENTAL },
+ { "use-queryport-pool", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "validate-except", &cfg_type_namelist, 0 },
+ { "v6-bias", &cfg_type_uint32, 0 },
+ { "zero-no-soa-ttl-cache", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found within the 'view' statement only.
+ */
+static cfg_clausedef_t view_only_clauses[] = {
+ { "match-clients", &cfg_type_bracketed_aml, 0 },
+ { "match-destinations", &cfg_type_bracketed_aml, 0 },
+ { "match-recursive-only", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Sig-validity-interval.
+ */
+
+static cfg_tuplefielddef_t validityinterval_fields[] = {
+ { "validity", &cfg_type_uint32, 0 },
+ { "re-sign", &cfg_type_optional_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_validityinterval = {
+ "validityinterval", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, validityinterval_fields
+};
+
+/*%
+ * Clauses that can be found in a 'dnssec-policy' statement.
+ */
+static cfg_clausedef_t dnssecpolicy_clauses[] = {
+ { "dnskey-ttl", &cfg_type_duration, 0 },
+ { "keys", &cfg_type_kaspkeys, 0 },
+ { "max-zone-ttl", &cfg_type_duration, 0 },
+ { "nsec3param", &cfg_type_nsec3, 0 },
+ { "parent-ds-ttl", &cfg_type_duration, 0 },
+ { "parent-propagation-delay", &cfg_type_duration, 0 },
+ { "parent-registration-delay", &cfg_type_duration,
+ CFG_CLAUSEFLAG_OBSOLETE },
+ { "publish-safety", &cfg_type_duration, 0 },
+ { "purge-keys", &cfg_type_duration, 0 },
+ { "retire-safety", &cfg_type_duration, 0 },
+ { "signatures-refresh", &cfg_type_duration, 0 },
+ { "signatures-validity", &cfg_type_duration, 0 },
+ { "signatures-validity-dnskey", &cfg_type_duration, 0 },
+ { "zone-propagation-delay", &cfg_type_duration, 0 },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found in a 'zone' statement,
+ * with defaults in the 'view' or 'options' statement.
+ *
+ * Note: CFG_ZONE_* options indicate in which zone types this clause is
+ * legal.
+ */
+/*
+ * NOTE: To enable syntax which allows specifying port and protocol
+ * within 'allow-*' clauses, replace 'cfg_type_bracketed_aml' with
+ * 'cfg_type_transport_acl'.
+ *
+ * Example: allow-transfer port 853 protocol tls { ... };
+ */
+static cfg_clausedef_t zone_clauses[] = {
+ { "allow-notify", &cfg_type_bracketed_aml,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "allow-query", &cfg_type_bracketed_aml,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT | CFG_ZONE_STATICSTUB },
+ { "allow-query-on", &cfg_type_bracketed_aml,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT | CFG_ZONE_STATICSTUB },
+ { "allow-transfer", &cfg_type_transport_acl,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "allow-update", &cfg_type_bracketed_aml, CFG_ZONE_PRIMARY },
+ { "allow-update-forwarding", &cfg_type_bracketed_aml,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "also-notify", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "alt-transfer-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "alt-transfer-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "auto-dnssec", &cfg_type_autodnssec,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_CLAUSEFLAG_DEPRECATED },
+ { "check-dup-records", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-integrity", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "check-mx", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-mx-cname", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-sibling", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "check-spf", &cfg_type_warn, CFG_ZONE_PRIMARY },
+ { "check-srv-cname", &cfg_type_checkmode, CFG_ZONE_PRIMARY },
+ { "check-wildcard", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "dialup", &cfg_type_dialuptype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "dnssec-dnskey-kskonly", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-loadkeys-interval", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-policy", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnssec-secure-to-insecure", &cfg_type_boolean, CFG_ZONE_PRIMARY },
+ { "dnssec-update-mode", &cfg_type_dnssecupdatemode,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "forward", &cfg_type_forwardtype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_ZONE_STATICSTUB | CFG_ZONE_FORWARD },
+ { "forwarders", &cfg_type_portiplist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_STUB |
+ CFG_ZONE_STATICSTUB | CFG_ZONE_FORWARD },
+ { "key-directory", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "maintain-ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "masterfile-format", &cfg_type_masterformat,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT },
+ { "masterfile-style", &cfg_type_masterstyle,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_REDIRECT },
+ { "max-ixfr-log-size", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "max-ixfr-ratio", &cfg_type_ixfrratio,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "max-journal-size", &cfg_type_size,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "max-records", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
+ { "max-refresh-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-retry-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-idle-in", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-idle-out", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_MIRROR | CFG_ZONE_SECONDARY },
+ { "max-transfer-time-in", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "max-transfer-time-out", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_MIRROR | CFG_ZONE_SECONDARY },
+ { "max-zone-ttl", &cfg_type_maxduration,
+ CFG_ZONE_PRIMARY | CFG_ZONE_REDIRECT },
+ { "min-refresh-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "min-retry-time", &cfg_type_uint32,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "multi-master", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "notify", &cfg_type_notifytype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-delay", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "notify-to-soa", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "nsec3-test-zone", &cfg_type_boolean,
+ CFG_CLAUSEFLAG_TESTONLY | CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "parental-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "parental-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "request-expire", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "request-ixfr", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "serial-update-method", &cfg_type_updatemethod, CFG_ZONE_PRIMARY },
+ { "sig-signing-nodes", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-signing-signatures", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-signing-type", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "sig-validity-interval", &cfg_type_validityinterval,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "dnskey-sig-validity", &cfg_type_uint32,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "transfer-source", &cfg_type_sockaddr4wild,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "transfer-source-v6", &cfg_type_sockaddr6wild,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
+ { "try-tcp-refresh", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "update-check-ksk", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "use-alt-transfer-source", &cfg_type_boolean,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "zero-no-soa-ttl", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "zone-statistics", &cfg_type_zonestat,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
+ { NULL, NULL, 0 }
+};
+
+/*%
+ * Clauses that can be found in a 'zone' statement only.
+ *
+ * Note: CFG_ZONE_* options indicate in which zone types this clause is
+ * legal.
+ */
+static cfg_clausedef_t zone_only_clauses[] = {
+ /*
+ * Note that the format of the check-names option is different between
+ * the zone options and the global/view options. Ugh.
+ */
+ { "type", &cfg_type_zonetype,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_DELEGATION |
+ CFG_ZONE_HINT | CFG_ZONE_REDIRECT | CFG_ZONE_FORWARD },
+ { "check-names", &cfg_type_checkmode,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_HINT | CFG_ZONE_STUB },
+ { "database", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB },
+ { "delegation-only", &cfg_type_boolean,
+ CFG_ZONE_HINT | CFG_ZONE_STUB | CFG_ZONE_FORWARD |
+ CFG_CLAUSEFLAG_DEPRECATED },
+ { "dlz", &cfg_type_astring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_REDIRECT },
+ { "file", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
+ CFG_ZONE_STUB | CFG_ZONE_HINT | CFG_ZONE_REDIRECT },
+ { "in-view", &cfg_type_astring, CFG_ZONE_INVIEW },
+ { "inline-signing", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "ixfr-base", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "ixfr-from-differences", &cfg_type_boolean,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "ixfr-tmp-file", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "journal", &cfg_type_qstring,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
+ { "masters", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_ZONE_REDIRECT | CFG_CLAUSEFLAG_NODOC },
+ { "parental-agents", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
+ { "primaries", &cfg_type_namesockaddrkeylist,
+ CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB |
+ CFG_ZONE_REDIRECT },
+ { "pubkey", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "server-addresses", &cfg_type_bracketed_netaddrlist,
+ CFG_ZONE_STATICSTUB },
+ { "server-names", &cfg_type_namelist, CFG_ZONE_STATICSTUB },
+ { "update-policy", &cfg_type_updatepolicy, CFG_ZONE_PRIMARY },
+ { NULL, NULL, 0 }
+};
+
+/*% The top-level named.conf syntax. */
+
+static cfg_clausedef_t *namedconf_clausesets[] = { namedconf_clauses,
+ namedconf_or_view_clauses,
+ NULL };
+cfg_type_t cfg_type_namedconf = { "namedconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, namedconf_clausesets };
+
+/*% The bind.keys syntax (trust-anchors/managed-keys/trusted-keys only). */
+static cfg_clausedef_t *bindkeys_clausesets[] = { bindkeys_clauses, NULL };
+cfg_type_t cfg_type_bindkeys = { "bindkeys", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, bindkeys_clausesets };
+
+/*% The "options" statement syntax. */
+
+static cfg_clausedef_t *options_clausesets[] = { options_clauses, view_clauses,
+ zone_clauses, NULL };
+static cfg_type_t cfg_type_options = { "options", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, options_clausesets };
+
+/*% The "view" statement syntax. */
+
+static cfg_clausedef_t *view_clausesets[] = { view_only_clauses,
+ namedconf_or_view_clauses,
+ view_clauses, zone_clauses,
+ NULL };
+
+static cfg_type_t cfg_type_viewopts = { "view", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, view_clausesets };
+
+/*% The "zone" statement syntax. */
+
+static cfg_clausedef_t *zone_clausesets[] = { zone_only_clauses, zone_clauses,
+ NULL };
+cfg_type_t cfg_type_zoneopts = { "zoneopts", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, zone_clausesets };
+
+/*% The "dnssec-policy" statement syntax. */
+static cfg_clausedef_t *dnssecpolicy_clausesets[] = { dnssecpolicy_clauses,
+ NULL };
+cfg_type_t cfg_type_dnssecpolicyopts = {
+ "dnssecpolicyopts", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, dnssecpolicy_clausesets
+};
+
+/*% The "dynamically loadable zones" statement syntax. */
+
+static cfg_clausedef_t dlz_clauses[] = { { "database", &cfg_type_astring, 0 },
+ { "search", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 } };
+static cfg_clausedef_t *dlz_clausesets[] = { dlz_clauses, NULL };
+static cfg_type_t cfg_type_dlz = { "dlz", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, dlz_clausesets };
+
+/*%
+ * The "dyndb" statement syntax.
+ */
+
+static cfg_tuplefielddef_t dyndb_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "library", &cfg_type_qstring, 0 },
+ { "parameters", &cfg_type_bracketed_text, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_dyndb = { "dyndb", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, dyndb_fields };
+
+/*%
+ * The "plugin" statement syntax.
+ * Currently only one plugin type is supported: query.
+ */
+
+static const char *plugin_enums[] = { "query", NULL };
+static cfg_type_t cfg_type_plugintype = { "plugintype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, plugin_enums };
+static cfg_tuplefielddef_t plugin_fields[] = {
+ { "type", &cfg_type_plugintype, 0 },
+ { "library", &cfg_type_astring, 0 },
+ { "parameters", &cfg_type_optional_bracketed_text, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_plugin = { "plugin", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, plugin_fields };
+
+/*%
+ * Clauses that can be found within the 'key' statement.
+ */
+static cfg_clausedef_t key_clauses[] = { { "algorithm", &cfg_type_astring, 0 },
+ { "secret", &cfg_type_sstring, 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_clausedef_t *key_clausesets[] = { key_clauses, NULL };
+static cfg_type_t cfg_type_key = { "key", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, key_clausesets };
+
+/*%
+ * Clauses that can be found in a 'server' statement.
+ *
+ * Please update lib/bind9/check.c and
+ * bin/tests/system/checkconf/good-server-christmas-tree.conf.in to
+ * exercise the new clause when adding new clauses.
+ */
+static cfg_clausedef_t server_clauses[] = {
+ { "bogus", &cfg_type_boolean, 0 },
+ { "edns", &cfg_type_boolean, 0 },
+ { "edns-udp-size", &cfg_type_uint32, 0 },
+ { "edns-version", &cfg_type_uint32, 0 },
+ { "keys", &cfg_type_server_key_kludge, 0 },
+ { "max-udp-size", &cfg_type_uint32, 0 },
+ { "notify-source", &cfg_type_sockaddr4wild, 0 },
+ { "notify-source-v6", &cfg_type_sockaddr6wild, 0 },
+ { "padding", &cfg_type_uint32, 0 },
+ { "provide-ixfr", &cfg_type_boolean, 0 },
+ { "query-source", &cfg_type_querysource4, 0 },
+ { "query-source-v6", &cfg_type_querysource6, 0 },
+ { "request-expire", &cfg_type_boolean, 0 },
+ { "request-ixfr", &cfg_type_boolean, 0 },
+ { "request-nsid", &cfg_type_boolean, 0 },
+ { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "send-cookie", &cfg_type_boolean, 0 },
+ { "support-ixfr", NULL, CFG_CLAUSEFLAG_ANCIENT },
+ { "tcp-keepalive", &cfg_type_boolean, 0 },
+ { "tcp-only", &cfg_type_boolean, 0 },
+ { "transfer-format", &cfg_type_transferformat, 0 },
+ { "transfer-source", &cfg_type_sockaddr4wild, 0 },
+ { "transfer-source-v6", &cfg_type_sockaddr6wild, 0 },
+ { "transfers", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *server_clausesets[] = { server_clauses, NULL };
+static cfg_type_t cfg_type_server = { "server", cfg_parse_netprefix_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, server_clausesets };
+
+/*%
+ * Clauses that can be found in a 'channel' clause in the
+ * 'logging' statement.
+ *
+ * These have some additional constraints that need to be
+ * checked after parsing:
+ * - There must exactly one of file/syslog/null/stderr
+ */
+
+static const char *printtime_enums[] = { "iso8601", "iso8601-utc", "local",
+ NULL };
+static isc_result_t
+parse_printtime(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_printtime(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_printtime = { "printtime", parse_printtime,
+ cfg_print_ustring, doc_printtime,
+ &cfg_rep_string, printtime_enums };
+
+static cfg_clausedef_t channel_clauses[] = {
+ /* Destinations. We no longer require these to be first. */
+ { "file", &cfg_type_logfile, 0 },
+ { "syslog", &cfg_type_optional_facility, 0 },
+ { "null", &cfg_type_void, 0 },
+ { "stderr", &cfg_type_void, 0 },
+ /* Options. We now accept these for the null channel, too. */
+ { "severity", &cfg_type_logseverity, 0 },
+ { "print-time", &cfg_type_printtime, 0 },
+ { "print-severity", &cfg_type_boolean, 0 },
+ { "print-category", &cfg_type_boolean, 0 },
+ { "buffered", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *channel_clausesets[] = { channel_clauses, NULL };
+static cfg_type_t cfg_type_channel = { "channel", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, channel_clausesets };
+
+/*% A list of log destination, used in the "category" clause. */
+static cfg_type_t cfg_type_destinationlist = { "destinationlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+/*%
+ * Clauses that can be found in a 'logging' statement.
+ */
+static cfg_clausedef_t logging_clauses[] = {
+ { "channel", &cfg_type_channel, CFG_CLAUSEFLAG_MULTI },
+ { "category", &cfg_type_category, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+static cfg_clausedef_t *logging_clausesets[] = { logging_clauses, NULL };
+static cfg_type_t cfg_type_logging = { "logging", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, logging_clausesets };
+
+/*%
+ * For parsing an 'addzone' statement
+ */
+static cfg_tuplefielddef_t addzone_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "class", &cfg_type_optional_class, 0 },
+ { "view", &cfg_type_optional_class, 0 },
+ { "options", &cfg_type_zoneopts, 0 },
+ { NULL, NULL, 0 }
+};
+static cfg_type_t cfg_type_addzone = { "zone", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, addzone_fields };
+
+static cfg_clausedef_t addzoneconf_clauses[] = {
+ { "zone", &cfg_type_addzone, CFG_CLAUSEFLAG_MULTI }, { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *addzoneconf_clausesets[] = { addzoneconf_clauses,
+ NULL };
+
+cfg_type_t cfg_type_addzoneconf = { "addzoneconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, addzoneconf_clausesets };
+
+static isc_result_t
+parse_unitstring(char *str, isc_resourcevalue_t *valuep) {
+ char *endp;
+ unsigned int len;
+ uint64_t value;
+ uint64_t unit;
+
+ value = strtoull(str, &endp, 10);
+ if (*endp == 0) {
+ *valuep = value;
+ return (ISC_R_SUCCESS);
+ }
+
+ len = strlen(str);
+ if (len < 2 || endp[1] != '\0') {
+ return (ISC_R_FAILURE);
+ }
+
+ switch (str[len - 1]) {
+ case 'k':
+ case 'K':
+ unit = 1024;
+ break;
+ case 'm':
+ case 'M':
+ unit = 1024 * 1024;
+ break;
+ case 'g':
+ case 'G':
+ unit = 1024 * 1024 * 1024;
+ break;
+ default:
+ return (ISC_R_FAILURE);
+ }
+ if (value > ((uint64_t)UINT64_MAX / unit)) {
+ return (ISC_R_FAILURE);
+ }
+ *valuep = value * unit;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+parse_sizeval(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ uint64_t val;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ CHECK(parse_unitstring(TOKEN_STRING(pctx), &val));
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint64, &obj));
+ obj->value.uint64 = val;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer and optional unit");
+ return (result);
+}
+
+static isc_result_t
+parse_sizeval_percent(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ char *endp;
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ uint64_t val;
+ uint64_t percent;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ percent = strtoull(TOKEN_STRING(pctx), &endp, 10);
+
+ if (*endp == '%' && *(endp + 1) == 0) {
+ CHECK(cfg_create_obj(pctx, &cfg_type_percentage, &obj));
+ obj->value.uint32 = (uint32_t)percent;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ } else {
+ CHECK(parse_unitstring(TOKEN_STRING(pctx), &val));
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint64, &obj));
+ obj->value.uint64 = val;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected integer and optional unit or percent");
+ return (result);
+}
+
+static void
+doc_sizeval_percent(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "( ");
+ cfg_doc_terminal(pctx, &cfg_type_size);
+ cfg_print_cstr(pctx, " | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+/*%
+ * A size value (number + optional unit).
+ */
+static cfg_type_t cfg_type_sizeval = { "sizeval", parse_sizeval,
+ cfg_print_uint64, cfg_doc_terminal,
+ &cfg_rep_uint64, NULL };
+
+/*%
+ * A size, "unlimited", or "default".
+ */
+
+static isc_result_t
+parse_size(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_sizeval, ret));
+}
+
+static void
+doc_size(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_sizeval);
+}
+
+static const char *size_enums[] = { "default", "unlimited", NULL };
+static cfg_type_t cfg_type_size = {
+ "size", parse_size, cfg_print_ustring,
+ doc_size, &cfg_rep_string, size_enums
+};
+
+/*%
+ * A size or "unlimited", but not "default".
+ */
+static const char *sizenodefault_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_sizenodefault = {
+ "size_no_default", parse_size, cfg_print_ustring,
+ doc_size, &cfg_rep_string, sizenodefault_enums
+};
+
+/*%
+ * A size in absolute values or percents.
+ */
+static cfg_type_t cfg_type_sizeval_percent = {
+ "sizeval_percent", parse_sizeval_percent, cfg_print_ustring,
+ doc_sizeval_percent, &cfg_rep_string, NULL
+};
+
+/*%
+ * A size in absolute values or percents, or "unlimited", or "default"
+ */
+
+static isc_result_t
+parse_size_or_percent(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_sizeval_percent,
+ ret));
+}
+
+static void
+doc_parse_size_or_percent(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( default | unlimited | ");
+ cfg_doc_terminal(pctx, &cfg_type_sizeval);
+ cfg_print_cstr(pctx, " | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+static const char *sizeorpercent_enums[] = { "default", "unlimited", NULL };
+static cfg_type_t cfg_type_sizeorpercent = {
+ "size_or_percent", parse_size_or_percent, cfg_print_ustring,
+ doc_parse_size_or_percent, &cfg_rep_string, sizeorpercent_enums
+};
+
+/*%
+ * An IXFR size ratio: percentage, or "unlimited".
+ */
+
+static isc_result_t
+parse_ixfrratio(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_percentage, ret));
+}
+
+static void
+doc_ixfrratio(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( unlimited | ");
+ cfg_doc_terminal(pctx, &cfg_type_percentage);
+ cfg_print_cstr(pctx, " )");
+}
+
+static const char *ixfrratio_enums[] = { "unlimited", NULL };
+static cfg_type_t cfg_type_ixfrratio = { "ixfr_ratio", parse_ixfrratio,
+ NULL, doc_ixfrratio,
+ NULL, ixfrratio_enums };
+
+/*%
+ * optional_keyvalue
+ */
+static isc_result_t
+parse_maybe_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ bool optional, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const keyword_type_t *kw = type->of;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), kw->name) == 0)
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(kw->type->parse(pctx, kw->type, &obj));
+ obj->type = type; /* XXX kludge */
+ } else {
+ if (optional) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected '%s'",
+ kw->name);
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ }
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (parse_maybe_optional_keyvalue(pctx, type, false, ret));
+}
+
+static isc_result_t
+parse_optional_keyvalue(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_maybe_optional_keyvalue(pctx, type, true, ret));
+}
+
+static void
+print_keyvalue(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const keyword_type_t *kw = obj->type->of;
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ kw->type->print(pctx, obj);
+}
+
+static void
+doc_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+}
+
+static void
+doc_optional_keyvalue(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, "[ ");
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static const char *dialup_enums[] = { "notify", "notify-passive", "passive",
+ "refresh", NULL };
+static isc_result_t
+parse_dialup_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_dialup_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_dialuptype = { "dialuptype", parse_dialup_type,
+ cfg_print_ustring, doc_dialup_type,
+ &cfg_rep_string, dialup_enums };
+
+static const char *notify_enums[] = { "explicit", "master-only", "primary-only",
+ NULL };
+static isc_result_t
+parse_notify_type(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_notify_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_notifytype = {
+ "notifytype", parse_notify_type, cfg_print_ustring,
+ doc_notify_type, &cfg_rep_string, notify_enums,
+};
+
+static const char *minimal_enums[] = { "no-auth", "no-auth-recursive", NULL };
+static isc_result_t
+parse_minimal(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_minimal(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_minimal = {
+ "minimal", parse_minimal, cfg_print_ustring,
+ doc_minimal, &cfg_rep_string, minimal_enums,
+};
+
+static const char *ixfrdiff_enums[] = { "primary", "master", "secondary",
+ "slave", NULL };
+static isc_result_t
+parse_ixfrdiff_type(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_boolean, ret));
+}
+static void
+doc_ixfrdiff_type(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_boolean);
+}
+static cfg_type_t cfg_type_ixfrdifftype = {
+ "ixfrdiff", parse_ixfrdiff_type, cfg_print_ustring,
+ doc_ixfrdiff_type, &cfg_rep_string, ixfrdiff_enums,
+};
+
+static keyword_type_t key_kw = { "key", &cfg_type_astring };
+
+cfg_type_t cfg_type_keyref = { "keyref", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_string, &key_kw };
+
+static cfg_type_t cfg_type_optional_keyref = {
+ "optional_keyref", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &key_kw
+};
+
+static const char *qminmethod_enums[] = { "strict", "relaxed", "disabled",
+ "off", NULL };
+
+static cfg_type_t cfg_type_qminmethod = { "qminmethod", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, qminmethod_enums };
+
+/*%
+ * A "controls" statement is represented as a map with the multivalued
+ * "inet" and "unix" clauses.
+ */
+
+static keyword_type_t controls_allow_kw = { "allow", &cfg_type_bracketed_aml };
+
+static cfg_type_t cfg_type_controls_allow = {
+ "controls_allow", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_list, &controls_allow_kw
+};
+
+static keyword_type_t controls_keys_kw = { "keys", &cfg_type_keylist };
+
+static cfg_type_t cfg_type_controls_keys = {
+ "controls_keys", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_list, &controls_keys_kw
+};
+
+static keyword_type_t controls_readonly_kw = { "read-only", &cfg_type_boolean };
+
+static cfg_type_t cfg_type_controls_readonly = {
+ "controls_readonly", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_boolean, &controls_readonly_kw
+};
+
+static cfg_tuplefielddef_t inetcontrol_fields[] = {
+ { "address", &cfg_type_controls_sockaddr, 0 },
+ { "allow", &cfg_type_controls_allow, 0 },
+ { "keys", &cfg_type_controls_keys, 0 },
+ { "read-only", &cfg_type_controls_readonly, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_inetcontrol = {
+ "inetcontrol", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, inetcontrol_fields
+};
+
+static keyword_type_t controls_perm_kw = { "perm", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_perm = {
+ "controls_perm", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_perm_kw
+};
+
+static keyword_type_t controls_owner_kw = { "owner", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_owner = {
+ "controls_owner", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_owner_kw
+};
+
+static keyword_type_t controls_group_kw = { "group", &cfg_type_uint32 };
+
+static cfg_type_t cfg_type_controls_group = {
+ "controls_allow", parse_keyvalue, print_keyvalue,
+ doc_keyvalue, &cfg_rep_uint32, &controls_group_kw
+};
+
+static cfg_tuplefielddef_t unixcontrol_fields[] = {
+ { "path", &cfg_type_qstring, 0 },
+ { "perm", &cfg_type_controls_perm, 0 },
+ { "owner", &cfg_type_controls_owner, 0 },
+ { "group", &cfg_type_controls_group, 0 },
+ { "keys", &cfg_type_controls_keys, 0 },
+ { "read-only", &cfg_type_controls_readonly, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_unixcontrol = {
+ "unixcontrol", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, unixcontrol_fields
+};
+
+static cfg_clausedef_t controls_clauses[] = {
+ { "inet", &cfg_type_inetcontrol, CFG_CLAUSEFLAG_MULTI },
+ { "unix", &cfg_type_unixcontrol, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *controls_clausesets[] = { controls_clauses, NULL };
+static cfg_type_t cfg_type_controls = { "controls", cfg_parse_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, &controls_clausesets };
+
+/*%
+ * A "statistics-channels" statement is represented as a map with the
+ * multivalued "inet" clauses.
+ */
+static void
+doc_optional_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const keyword_type_t *kw = type->of;
+ cfg_print_cstr(pctx, "[ ");
+ cfg_print_cstr(pctx, kw->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, kw->type);
+ cfg_print_cstr(pctx, " ]");
+}
+
+static cfg_type_t cfg_type_optional_allow = {
+ "optional_allow", parse_optional_keyvalue,
+ print_keyvalue, doc_optional_bracketed_list,
+ &cfg_rep_list, &controls_allow_kw
+};
+
+static cfg_tuplefielddef_t statserver_fields[] = {
+ { "address", &cfg_type_controls_sockaddr, 0 }, /* reuse controls def */
+ { "allow", &cfg_type_optional_allow, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_statschannel = {
+ "statschannel", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, statserver_fields
+};
+
+static cfg_clausedef_t statservers_clauses[] = {
+ { "inet", &cfg_type_statschannel, CFG_CLAUSEFLAG_MULTI },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *statservers_clausesets[] = { statservers_clauses,
+ NULL };
+
+static cfg_type_t cfg_type_statschannels = {
+ "statistics-channels", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, &statservers_clausesets
+};
+
+/*%
+ * An optional class, as used in view and zone statements.
+ */
+static isc_result_t
+parse_optional_class(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_ustring, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_class(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <class> ]");
+}
+
+static cfg_type_t cfg_type_optional_class = { "optional_class",
+ parse_optional_class,
+ NULL,
+ doc_optional_class,
+ NULL,
+ NULL };
+
+static isc_result_t
+parse_querysource(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isc_netaddr_t netaddr;
+ in_port_t port = 0;
+ unsigned int have_address = 0;
+ unsigned int have_port = 0;
+ unsigned int have_dscp = 0;
+ const unsigned int *flagp = type->of;
+ int dscp = -1;
+
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ isc_netaddr_any(&netaddr);
+ } else if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ isc_netaddr_any6(&netaddr);
+ } else {
+ UNREACHABLE();
+ }
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ if (strcasecmp(TOKEN_STRING(pctx), "address") == 0) {
+ /* read "address" */
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_rawaddr(pctx, *flagp,
+ &netaddr));
+ have_address++;
+ } else if (strcasecmp(TOKEN_STRING(pctx), "port") == 0)
+ {
+ /* read "port" */
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'port' is deprecated");
+ }
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_rawport(pctx, CFG_ADDR_WILDOK,
+ &port));
+ have_port++;
+ } else if (strcasecmp(TOKEN_STRING(pctx), "dscp") == 0)
+ {
+ /* read "dscp" */
+ cfg_parser_warning(pctx, 0,
+ "'dscp' is obsolete and "
+ "should be removed");
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_uint32(pctx, NULL, &obj));
+ dscp = cfg_obj_asuint32(obj);
+ cfg_obj_destroy(pctx, &obj);
+ have_dscp++;
+ } else if (have_port == 0 && have_dscp == 0 &&
+ have_address == 0)
+ {
+ return (cfg_parse_sockaddr(pctx, type, ret));
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected 'address' "
+ "or 'port'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ } else {
+ break;
+ }
+ }
+ if (have_address > 1 || have_port > 1 || have_address + have_port == 0)
+ {
+ cfg_parser_error(pctx, 0, "expected one address and/or port");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ if (have_dscp > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one dscp");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_querysource, &obj));
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
+ obj->value.sockaddrdscp.dscp = dscp;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "invalid query source");
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_querysource(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isc_netaddr_t na;
+ isc_netaddr_fromsockaddr(&na, &obj->value.sockaddr);
+ cfg_print_cstr(pctx, "address ");
+ cfg_print_rawaddr(pctx, &na);
+ cfg_print_cstr(pctx, " port ");
+ cfg_print_rawuint(pctx, isc_sockaddr_getport(&obj->value.sockaddr));
+ if (obj->value.sockaddrdscp.dscp != -1) {
+ cfg_print_cstr(pctx, " dscp ");
+ cfg_print_rawuint(pctx, obj->value.sockaddrdscp.dscp);
+ }
+}
+
+static void
+doc_querysource(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp = type->of;
+
+ cfg_print_cstr(pctx, "[ address ] ( ");
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ } else if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ } else {
+ UNREACHABLE();
+ }
+ cfg_print_cstr(pctx, " | * )");
+}
+
+static unsigned int sockaddr4wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V4OK |
+ CFG_ADDR_DSCPOK;
+static unsigned int sockaddr6wild_flags = CFG_ADDR_WILDOK | CFG_ADDR_V6OK |
+ CFG_ADDR_DSCPOK;
+
+static cfg_type_t cfg_type_querysource4 = {
+ "querysource4", parse_querysource, NULL, doc_querysource,
+ NULL, &sockaddr4wild_flags
+};
+
+static cfg_type_t cfg_type_querysource6 = {
+ "querysource6", parse_querysource, NULL, doc_querysource,
+ NULL, &sockaddr6wild_flags
+};
+
+static cfg_type_t cfg_type_querysource = { "querysource", NULL,
+ print_querysource, NULL,
+ &cfg_rep_sockaddr, NULL };
+
+/*%
+ * The socket address syntax in the "controls" statement is silly.
+ * It allows both socket address families, but also allows "*",
+ * which is gratuitously interpreted as the IPv4 wildcard address.
+ */
+static unsigned int controls_sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_WILDOK | CFG_ADDR_PORTOK;
+static cfg_type_t cfg_type_controls_sockaddr = {
+ "controls_sockaddr", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &controls_sockaddr_flags
+};
+
+/*%
+ * Handle the special kludge syntax of the "keys" clause in the "server"
+ * statement, which takes a single key with or without braces and semicolon.
+ */
+static isc_result_t
+parse_server_key_kludge(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ bool braces = false;
+ UNUSED(type);
+
+ /* Allow opening brace. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '{')
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ braces = true;
+ }
+
+ CHECK(cfg_parse_obj(pctx, &cfg_type_astring, ret));
+
+ if (braces) {
+ /* Skip semicolon if present. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == ';')
+ {
+ CHECK(cfg_gettoken(pctx, 0));
+ }
+
+ CHECK(cfg_parse_special(pctx, '}'));
+ }
+cleanup:
+ return (result);
+}
+static cfg_type_t cfg_type_server_key_kludge = {
+ "server_key", parse_server_key_kludge, NULL, cfg_doc_terminal, NULL,
+ NULL
+};
+
+/*%
+ * An optional logging facility.
+ */
+
+static isc_result_t
+parse_optional_facility(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_astring, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+doc_optional_facility(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ <syslog_facility> ]");
+}
+
+static cfg_type_t cfg_type_optional_facility = { "optional_facility",
+ parse_optional_facility,
+ NULL,
+ doc_optional_facility,
+ NULL,
+ NULL };
+
+/*%
+ * A log severity. Return as a string, except "debug N",
+ * which is returned as a keyword object.
+ */
+
+static keyword_type_t debug_kw = { "debug", &cfg_type_uint32 };
+static cfg_type_t cfg_type_debuglevel = { "debuglevel", parse_keyvalue,
+ print_keyvalue, doc_keyvalue,
+ &cfg_rep_uint32, &debug_kw };
+
+static isc_result_t
+parse_logseverity(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ strcasecmp(TOKEN_STRING(pctx), "debug") == 0)
+ {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "debug" */
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_NUMBER));
+ if (pctx->token.type == isc_tokentype_number) {
+ CHECK(cfg_parse_uint32(pctx, NULL, ret));
+ } else {
+ /*
+ * The debug level is optional and defaults to 1.
+ * This makes little sense, but we support it for
+ * compatibility with BIND 8.
+ */
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint32, ret));
+ (*ret)->value.uint32 = 1;
+ }
+ (*ret)->type = &cfg_type_debuglevel; /* XXX kludge */
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_loglevel, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static cfg_type_t cfg_type_logseverity = { "log_severity", parse_logseverity,
+ NULL, cfg_doc_terminal,
+ NULL, NULL };
+
+/*%
+ * The "file" clause of the "channel" statement.
+ * This is yet another special case.
+ */
+
+static const char *logversions_enums[] = { "unlimited", NULL };
+static isc_result_t
+parse_logversions(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ return (cfg_parse_enum_or_other(pctx, type, &cfg_type_uint32, ret));
+}
+
+static void
+doc_logversions(cfg_printer_t *pctx, const cfg_type_t *type) {
+ cfg_doc_enum_or_other(pctx, type, &cfg_type_uint32);
+}
+
+static cfg_type_t cfg_type_logversions = {
+ "logversions", parse_logversions, cfg_print_ustring,
+ doc_logversions, &cfg_rep_string, logversions_enums
+};
+
+static const char *logsuffix_enums[] = { "increment", "timestamp", NULL };
+static cfg_type_t cfg_type_logsuffix = { "logsuffix", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &logsuffix_enums };
+
+static cfg_tuplefielddef_t logfile_fields[] = {
+ { "file", &cfg_type_qstring, 0 },
+ { "versions", &cfg_type_logversions, 0 },
+ { "size", &cfg_type_size, 0 },
+ { "suffix", &cfg_type_logsuffix, 0 },
+ { NULL, NULL, 0 }
+};
+
+static isc_result_t
+parse_logfile(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const cfg_tuplefielddef_t *fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+
+ /* Parse the mandatory "file" field */
+ CHECK(cfg_parse_obj(pctx, fields[0].type, &obj->value.tuple[0]));
+
+ /* Parse "versions" and "size" fields in any order. */
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "versions") == 0 &&
+ obj->value.tuple[1] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "size") ==
+ 0 &&
+ obj->value.tuple[2] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ } else if (strcasecmp(TOKEN_STRING(pctx), "suffix") ==
+ 0 &&
+ obj->value.tuple[3] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[3].type,
+ &obj->value.tuple[3]));
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ /* Create void objects for missing optional values. */
+ if (obj->value.tuple[1] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ }
+ if (obj->value.tuple[2] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[2]));
+ }
+ if (obj->value.tuple[3] == NULL) {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[3]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_logfile(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_obj(pctx, obj->value.tuple[0]); /* file */
+ if (obj->value.tuple[1]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " versions ");
+ cfg_print_obj(pctx, obj->value.tuple[1]);
+ }
+ if (obj->value.tuple[2]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " size ");
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ }
+ if (obj->value.tuple[3]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " suffix ");
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+ }
+}
+
+static void
+doc_logfile(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "<quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ versions ( unlimited | <integer> ) ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ size <size> ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ suffix ( increment | timestamp ) ]");
+}
+
+static cfg_type_t cfg_type_logfile = { "log_file", parse_logfile,
+ print_logfile, doc_logfile,
+ &cfg_rep_tuple, logfile_fields };
+
+/*% An IPv4 address, "*" accepted as wildcard. */
+static cfg_type_t cfg_type_sockaddr4wild = {
+ "sockaddr4wild", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr4wild_flags
+};
+
+/*% An IPv6 address, "*" accepted as wildcard. */
+static cfg_type_t cfg_type_sockaddr6wild = {
+ "v6addrportwild", cfg_parse_sockaddr, cfg_print_sockaddr,
+ cfg_doc_sockaddr, &cfg_rep_sockaddr, &sockaddr6wild_flags
+};
+
+/*%
+ * rndc
+ */
+
+static cfg_clausedef_t rndcconf_options_clauses[] = {
+ { "default-key", &cfg_type_astring, 0 },
+ { "default-port", &cfg_type_uint32, 0 },
+ { "default-server", &cfg_type_astring, 0 },
+ { "default-source-address", &cfg_type_netaddr4wild, 0 },
+ { "default-source-address-v6", &cfg_type_netaddr6wild, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_options_clausesets[] = {
+ rndcconf_options_clauses, NULL
+};
+
+static cfg_type_t cfg_type_rndcconf_options = {
+ "rndcconf_options", cfg_parse_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rndcconf_options_clausesets
+};
+
+static cfg_clausedef_t rndcconf_server_clauses[] = {
+ { "key", &cfg_type_astring, 0 },
+ { "port", &cfg_type_uint32, 0 },
+ { "source-address", &cfg_type_netaddr4wild, 0 },
+ { "source-address-v6", &cfg_type_netaddr6wild, 0 },
+ { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_server_clausesets[] = {
+ rndcconf_server_clauses, NULL
+};
+
+static cfg_type_t cfg_type_rndcconf_server = {
+ "rndcconf_server", cfg_parse_named_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, rndcconf_server_clausesets
+};
+
+static cfg_clausedef_t rndcconf_clauses[] = {
+ { "key", &cfg_type_key, CFG_CLAUSEFLAG_MULTI },
+ { "server", &cfg_type_rndcconf_server, CFG_CLAUSEFLAG_MULTI },
+ { "options", &cfg_type_rndcconf_options, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rndcconf_clausesets[] = { rndcconf_clauses, NULL };
+
+cfg_type_t cfg_type_rndcconf = { "rndcconf", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndcconf_clausesets };
+
+static cfg_clausedef_t rndckey_clauses[] = { { "key", &cfg_type_key, 0 },
+ { NULL, NULL, 0 } };
+
+static cfg_clausedef_t *rndckey_clausesets[] = { rndckey_clauses, NULL };
+
+cfg_type_t cfg_type_rndckey = { "rndckey", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndckey_clausesets };
+
+/*
+ * session.key has exactly the same syntax as rndc.key, but it's defined
+ * separately for clarity (and so we can extend it someday, if needed).
+ */
+cfg_type_t cfg_type_sessionkey = { "sessionkey", cfg_parse_mapbody,
+ cfg_print_mapbody, cfg_doc_mapbody,
+ &cfg_rep_map, rndckey_clausesets };
+
+static cfg_tuplefielddef_t nameport_fields[] = {
+ { "name", &cfg_type_astring, 0 },
+ { "port", &cfg_type_optional_port, 0 },
+ { "dscp", &cfg_type_optional_dscp, CFG_CLAUSEFLAG_OBSOLETE },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nameport = { "nameport", cfg_parse_tuple,
+ cfg_print_tuple, cfg_doc_tuple,
+ &cfg_rep_tuple, nameport_fields };
+
+static void
+doc_sockaddrnameport(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( ");
+ cfg_print_cstr(pctx, "<quoted_string>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " )");
+}
+
+static isc_result_t
+parse_sockaddrnameport(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr,
+ ret));
+ } else {
+ const cfg_tuplefielddef_t *fields =
+ cfg_type_nameport.of;
+ CHECK(cfg_create_tuple(pctx, &cfg_type_nameport, &obj));
+ CHECK(cfg_parse_obj(pctx, fields[0].type,
+ &obj->value.tuple[0]));
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ CHECK(cfg_parse_obj(pctx, fields[2].type,
+ &obj->value.tuple[2]));
+ *ret = obj;
+ obj = NULL;
+ }
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address or hostname");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static cfg_type_t cfg_type_sockaddrnameport = { "sockaddrnameport_element",
+ parse_sockaddrnameport,
+ NULL,
+ doc_sockaddrnameport,
+ NULL,
+ NULL };
+
+static cfg_type_t cfg_type_bracketed_sockaddrnameportlist = {
+ "bracketed_sockaddrnameportlist",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_sockaddrnameport
+};
+
+/*%
+ * A list of socket addresses or name with an optional default port,
+ * as used in the dual-stack-servers option. E.g.,
+ * "port 1234 { dual-stack-servers.net; 10.0.0.1; 1::2 port 69; }"
+ */
+static cfg_tuplefielddef_t nameportiplist_fields[] = {
+ { "port", &cfg_type_optional_port, 0 },
+ { "addresses", &cfg_type_bracketed_sockaddrnameportlist, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_nameportiplist = {
+ "nameportiplist", cfg_parse_tuple, cfg_print_tuple,
+ cfg_doc_tuple, &cfg_rep_tuple, nameportiplist_fields
+};
+
+/*%
+ * remote servers element.
+ */
+
+static void
+doc_remoteselement(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "( ");
+ cfg_print_cstr(pctx, "<remote-servers>");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " | ");
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ cfg_print_cstr(pctx, " ");
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ cfg_print_cstr(pctx, " )");
+}
+
+static isc_result_t
+parse_remoteselement(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (cfg_lookingat_netaddr(pctx, CFG_ADDR_V4OK | CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_sockaddr(pctx, &cfg_type_sockaddr,
+ ret));
+ } else {
+ CHECK(cfg_parse_astring(pctx, &cfg_type_astring, ret));
+ }
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address or remote servers list "
+ "name");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static cfg_type_t cfg_type_remoteselement = { "remotes_element",
+ parse_remoteselement,
+ NULL,
+ doc_remoteselement,
+ NULL,
+ NULL };
+
+static int
+cmp_clause(const void *ap, const void *bp) {
+ const cfg_clausedef_t *a = (const cfg_clausedef_t *)ap;
+ const cfg_clausedef_t *b = (const cfg_clausedef_t *)bp;
+ return (strcmp(a->name, b->name));
+}
+
+bool
+cfg_clause_validforzone(const char *name, unsigned int ztype) {
+ const cfg_clausedef_t *clause;
+ bool valid = false;
+
+ for (clause = zone_clauses; clause->name != NULL; clause++) {
+ if ((clause->flags & ztype) == 0 ||
+ strcmp(clause->name, name) != 0)
+ {
+ continue;
+ }
+ valid = true;
+ }
+ for (clause = zone_only_clauses; clause->name != NULL; clause++) {
+ if ((clause->flags & ztype) == 0 ||
+ strcmp(clause->name, name) != 0)
+ {
+ continue;
+ }
+ valid = true;
+ }
+
+ return (valid);
+}
+
+void
+cfg_print_zonegrammar(const unsigned int zonetype, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+#define NCLAUSES \
+ (((sizeof(zone_clauses) + sizeof(zone_only_clauses)) / \
+ sizeof(clause[0])) - \
+ 1)
+
+ cfg_printer_t pctx;
+ cfg_clausedef_t *clause = NULL;
+ cfg_clausedef_t clauses[NCLAUSES];
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+
+ memmove(clauses, zone_clauses, sizeof(zone_clauses));
+ memmove(clauses + sizeof(zone_clauses) / sizeof(zone_clauses[0]) - 1,
+ zone_only_clauses, sizeof(zone_only_clauses));
+ qsort(clauses, NCLAUSES - 1, sizeof(clause[0]), cmp_clause);
+
+ cfg_print_cstr(&pctx, "zone <string> [ <class> ] {\n");
+ pctx.indent++;
+
+ switch (zonetype) {
+ case CFG_ZONE_PRIMARY:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type primary;\n");
+ break;
+ case CFG_ZONE_SECONDARY:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type secondary;\n");
+ break;
+ case CFG_ZONE_MIRROR:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type mirror;\n");
+ break;
+ case CFG_ZONE_STUB:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type stub;\n");
+ break;
+ case CFG_ZONE_HINT:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type hint;\n");
+ break;
+ case CFG_ZONE_FORWARD:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type forward;\n");
+ break;
+ case CFG_ZONE_STATICSTUB:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type static-stub;\n");
+ break;
+ case CFG_ZONE_REDIRECT:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type redirect;\n");
+ break;
+ case CFG_ZONE_DELEGATION:
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, "type delegation-only;\n");
+ break;
+ case CFG_ZONE_INVIEW:
+ /* no zone type is specified for these */
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ for (clause = clauses; clause->name != NULL; clause++) {
+ if (((pctx.flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+ (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+ {
+ continue;
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+ (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+ {
+ continue;
+ }
+
+ if ((clause->flags & zonetype) == 0 ||
+ strcasecmp(clause->name, "type") == 0)
+ {
+ continue;
+ }
+ cfg_print_indent(&pctx);
+ cfg_print_cstr(&pctx, clause->name);
+ cfg_print_cstr(&pctx, " ");
+ cfg_doc_obj(&pctx, clause->type);
+ cfg_print_cstr(&pctx, ";");
+ cfg_print_clauseflags(&pctx, clause->flags);
+ cfg_print_cstr(&pctx, "\n");
+ }
+
+ pctx.indent--;
+ cfg_print_cstr(&pctx, "};\n");
+}
+
+/*%
+ * "tls" and related statement syntax.
+ */
+static cfg_type_t cfg_type_tlsprotos = { "tls_protocols",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_astring };
+
+static cfg_clausedef_t tls_clauses[] = {
+ { "key-file", &cfg_type_qstring, 0 },
+ { "cert-file", &cfg_type_qstring, 0 },
+ { "ca-file", &cfg_type_qstring, 0 },
+ { "remote-hostname", &cfg_type_qstring, 0 },
+ { "dhparam-file", &cfg_type_qstring, 0 },
+ { "protocols", &cfg_type_tlsprotos, 0 },
+ { "ciphers", &cfg_type_astring, 0 },
+ { "prefer-server-ciphers", &cfg_type_boolean, 0 },
+ { "session-tickets", &cfg_type_boolean, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *tls_clausesets[] = { tls_clauses, NULL };
+static cfg_type_t cfg_type_tlsconf = { "tlsconf", cfg_parse_named_map,
+ cfg_print_map, cfg_doc_map,
+ &cfg_rep_map, tls_clausesets };
+
+static keyword_type_t tls_kw = { "tls", &cfg_type_astring };
+static cfg_type_t cfg_type_optional_tls = {
+ "tlsoptional", parse_optional_keyvalue, print_keyvalue,
+ doc_optional_keyvalue, &cfg_rep_string, &tls_kw
+};
+
+/* http and https */
+
+static cfg_type_t cfg_type_bracketed_http_endpoint_list = {
+ "bracketed_http_endpoint_list",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_qstring
+};
+
+static cfg_clausedef_t cfg_http_description_clauses[] = {
+ { "endpoints", &cfg_type_bracketed_http_endpoint_list, 0 },
+ { "listener-clients", &cfg_type_uint32, 0 },
+ { "streams-per-connection", &cfg_type_uint32, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *http_description_clausesets[] = {
+ cfg_http_description_clauses, NULL
+};
+
+static cfg_type_t cfg_type_http_description = {
+ "http_desc", cfg_parse_named_map, cfg_print_map,
+ cfg_doc_map, &cfg_rep_map, http_description_clausesets
+};
diff --git a/lib/isccfg/parser.c b/lib/isccfg/parser.c
new file mode 100644
index 0000000..8bee734
--- /dev/null
+++ b/lib/isccfg/parser.c
@@ -0,0 +1,3901 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0 AND BSD-2-Clause
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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) 2009-2018 NLNet Labs.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <isc/buffer.h>
+#include <isc/dir.h>
+#include <isc/errno.h>
+#include <isc/formatcheck.h>
+#include <isc/glob.h>
+#include <isc/lex.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/netmgr.h>
+#include <isc/netscope.h>
+#include <isc/print.h>
+#include <isc/sockaddr.h>
+#include <isc/string.h>
+#include <isc/symtab.h>
+#include <isc/util.h>
+
+#include <dns/ttl.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/log.h>
+
+/* Shorthand */
+#define CAT CFG_LOGCATEGORY_CONFIG
+#define MOD CFG_LOGMODULE_PARSER
+
+#define MAP_SYM 1 /* Unique type for isc_symtab */
+
+#define TOKEN_STRING(pctx) (pctx->token.value.as_textregion.base)
+
+/* Check a return value. */
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) \
+ goto cleanup; \
+ } while (0)
+
+/* Clean up a configuration object if non-NULL. */
+#define CLEANUP_OBJ(obj) \
+ do { \
+ if ((obj) != NULL) \
+ cfg_obj_destroy(pctx, &(obj)); \
+ } while (0)
+
+/*
+ * Forward declarations of static functions.
+ */
+
+static void
+free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+parse_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+print_list(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+free_list(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp);
+
+static isc_result_t
+create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
+ cfg_obj_t **ret);
+
+static void
+free_string(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **objp);
+
+static void
+free_map(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+parse_symtab_elt(cfg_parser_t *pctx, const char *name, cfg_type_t *elttype,
+ isc_symtab_t *symtab, bool callback);
+
+static void
+free_noop(cfg_parser_t *pctx, cfg_obj_t *obj);
+
+static isc_result_t
+cfg_getstringtoken(cfg_parser_t *pctx);
+
+static void
+parser_complain(cfg_parser_t *pctx, bool is_warning, unsigned int flags,
+ const char *format, va_list args);
+
+#if defined(HAVE_GEOIP2)
+static isc_result_t
+parse_geoip(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret);
+
+static void
+print_geoip(cfg_printer_t *pctx, const cfg_obj_t *obj);
+
+static void
+doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type);
+#endif /* HAVE_GEOIP2 */
+
+/*
+ * Data representations. These correspond to members of the
+ * "value" union in struct cfg_obj (except "void", which does
+ * not need a union member).
+ */
+
+cfg_rep_t cfg_rep_uint32 = { "uint32", free_noop };
+cfg_rep_t cfg_rep_uint64 = { "uint64", free_noop };
+cfg_rep_t cfg_rep_string = { "string", free_string };
+cfg_rep_t cfg_rep_boolean = { "boolean", free_noop };
+cfg_rep_t cfg_rep_map = { "map", free_map };
+cfg_rep_t cfg_rep_list = { "list", free_list };
+cfg_rep_t cfg_rep_tuple = { "tuple", free_tuple };
+cfg_rep_t cfg_rep_sockaddr = { "sockaddr", free_noop };
+cfg_rep_t cfg_rep_netprefix = { "netprefix", free_noop };
+cfg_rep_t cfg_rep_void = { "void", free_noop };
+cfg_rep_t cfg_rep_fixedpoint = { "fixedpoint", free_noop };
+cfg_rep_t cfg_rep_percentage = { "percentage", free_noop };
+cfg_rep_t cfg_rep_duration = { "duration", free_noop };
+
+/*
+ * Configuration type definitions.
+ */
+
+/*%
+ * An implicit list. These are formed by clauses that occur multiple times.
+ */
+static cfg_type_t cfg_type_implicitlist = { "implicitlist", NULL,
+ print_list, NULL,
+ &cfg_rep_list, NULL };
+
+/* Functions. */
+
+void
+cfg_print_obj(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ obj->type->print(pctx, obj);
+}
+
+void
+cfg_print_chars(cfg_printer_t *pctx, const char *text, int len) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(text != NULL);
+
+ pctx->f(pctx->closure, text, len);
+}
+
+static void
+print_open(cfg_printer_t *pctx) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_cstr(pctx, "{ ");
+ } else {
+ cfg_print_cstr(pctx, "{\n");
+ pctx->indent++;
+ }
+}
+
+void
+cfg_print_indent(cfg_printer_t *pctx) {
+ int indent = pctx->indent;
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_cstr(pctx, " ");
+ return;
+ }
+ while (indent > 0) {
+ cfg_print_cstr(pctx, "\t");
+ indent--;
+ }
+}
+
+static void
+print_close(cfg_printer_t *pctx) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ pctx->indent--;
+ cfg_print_indent(pctx);
+ }
+ cfg_print_cstr(pctx, "}");
+}
+
+isc_result_t
+cfg_parse_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ result = type->parse(pctx, type, ret);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ ENSURE(*ret != NULL);
+ return (ISC_R_SUCCESS);
+}
+
+void
+cfg_print(const cfg_obj_t *obj,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ REQUIRE(obj != NULL);
+ REQUIRE(f != NULL);
+
+ cfg_printx(obj, 0, f, closure);
+}
+
+void
+cfg_printx(const cfg_obj_t *obj, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ cfg_printer_t pctx;
+
+ REQUIRE(obj != NULL);
+ REQUIRE(f != NULL);
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+ obj->type->print(&pctx, obj);
+}
+
+/* Tuples. */
+
+isc_result_t
+cfg_create_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ cfg_obj_t *obj = NULL;
+ unsigned int nfields = 0;
+ int i;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ fields = type->of;
+
+ for (f = fields; f->name != NULL; f++) {
+ nfields++;
+ }
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ obj->value.tuple = isc_mem_get(pctx->mctx,
+ nfields * sizeof(cfg_obj_t *));
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ obj->value.tuple[i] = NULL;
+ }
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (obj != NULL) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ }
+ return (result);
+}
+
+isc_result_t
+cfg_parse_tuple(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ cfg_obj_t *obj = NULL;
+ unsigned int i;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ CHECK(cfg_parse_obj(pctx, f->type, &obj->value.tuple[i]));
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+void
+cfg_print_tuple(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ bool need_space = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ fields = obj->type->of;
+
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ const cfg_obj_t *fieldobj = obj->value.tuple[i];
+ if (need_space && fieldobj->type->rep != &cfg_rep_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_print_obj(pctx, fieldobj);
+ need_space = (need_space ||
+ fieldobj->type->print != cfg_print_void);
+ }
+}
+
+void
+cfg_doc_tuple(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+ bool need_space = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ fields = type->of;
+
+ for (f = fields; f->name != NULL; f++) {
+ if (need_space) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_doc_obj(pctx, f->type);
+ need_space = (f->type->print != cfg_print_void);
+ }
+}
+
+static void
+free_tuple(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields = obj->type->of;
+ const cfg_tuplefielddef_t *f;
+ unsigned int nfields = 0;
+
+ if (obj->value.tuple == NULL) {
+ return;
+ }
+
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ CLEANUP_OBJ(obj->value.tuple[i]);
+ nfields++;
+ }
+ isc_mem_put(pctx->mctx, obj->value.tuple,
+ nfields * sizeof(cfg_obj_t *));
+}
+
+bool
+cfg_obj_istuple(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_tuple);
+}
+
+const cfg_obj_t *
+cfg_tuple_get(const cfg_obj_t *tupleobj, const char *name) {
+ unsigned int i;
+ const cfg_tuplefielddef_t *fields;
+ const cfg_tuplefielddef_t *f;
+
+ REQUIRE(tupleobj != NULL && tupleobj->type->rep == &cfg_rep_tuple);
+ REQUIRE(name != NULL);
+
+ fields = tupleobj->type->of;
+ for (f = fields, i = 0; f->name != NULL; f++, i++) {
+ if (strcmp(f->name, name) == 0) {
+ return (tupleobj->value.tuple[i]);
+ }
+ }
+ UNREACHABLE();
+}
+
+isc_result_t
+cfg_parse_special(cfg_parser_t *pctx, int special) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == special)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "'%c' expected", special);
+ return (ISC_R_UNEXPECTEDTOKEN);
+cleanup:
+ return (result);
+}
+
+/*
+ * Parse a required semicolon. If it is not there, log
+ * an error and increment the error count but continue
+ * parsing. Since the next token is pushed back,
+ * care must be taken to make sure it is eventually
+ * consumed or an infinite loop may result.
+ */
+static isc_result_t
+parse_semicolon(cfg_parser_t *pctx) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == ';')
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_parser_error(pctx, CFG_LOG_BEFORE, "missing ';'");
+ cfg_ungettoken(pctx);
+cleanup:
+ return (result);
+}
+
+/*
+ * Parse EOF, logging and returning an error if not there.
+ */
+static isc_result_t
+parse_eof(cfg_parser_t *pctx) {
+ isc_result_t result;
+
+ CHECK(cfg_gettoken(pctx, 0));
+
+ if (pctx->token.type == isc_tokentype_eof) {
+ return (ISC_R_SUCCESS);
+ }
+
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "syntax error");
+ return (ISC_R_UNEXPECTEDTOKEN);
+cleanup:
+ return (result);
+}
+
+/* A list of files, used internally for pctx->files. */
+
+static cfg_type_t cfg_type_filelist = { "filelist", NULL,
+ print_list, NULL,
+ &cfg_rep_list, &cfg_type_qstring };
+
+isc_result_t
+cfg_parser_create(isc_mem_t *mctx, isc_log_t *lctx, cfg_parser_t **ret) {
+ isc_result_t result;
+ cfg_parser_t *pctx;
+ isc_lexspecials_t specials;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ pctx = isc_mem_get(mctx, sizeof(*pctx));
+
+ pctx->mctx = NULL;
+ isc_mem_attach(mctx, &pctx->mctx);
+
+ isc_refcount_init(&pctx->references, 1);
+
+ pctx->lctx = lctx;
+ pctx->lexer = NULL;
+ pctx->seen_eof = false;
+ pctx->ungotten = false;
+ pctx->errors = 0;
+ pctx->warnings = 0;
+ pctx->open_files = NULL;
+ pctx->closed_files = NULL;
+ pctx->line = 0;
+ pctx->callback = NULL;
+ pctx->callbackarg = NULL;
+ pctx->token.type = isc_tokentype_unknown;
+ pctx->flags = 0;
+ pctx->buf_name = NULL;
+
+ memset(specials, 0, sizeof(specials));
+ specials['{'] = 1;
+ specials['}'] = 1;
+ specials[';'] = 1;
+ specials['/'] = 1;
+ specials['"'] = 1;
+ specials['!'] = 1;
+
+ CHECK(isc_lex_create(pctx->mctx, 1024, &pctx->lexer));
+
+ isc_lex_setspecials(pctx->lexer, specials);
+ isc_lex_setcomments(pctx->lexer,
+ (ISC_LEXCOMMENT_C | ISC_LEXCOMMENT_CPLUSPLUS |
+ ISC_LEXCOMMENT_SHELL));
+
+ CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->open_files));
+ CHECK(cfg_create_list(pctx, &cfg_type_filelist, &pctx->closed_files));
+
+ *ret = pctx;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (pctx->lexer != NULL) {
+ isc_lex_destroy(&pctx->lexer);
+ }
+ CLEANUP_OBJ(pctx->open_files);
+ CLEANUP_OBJ(pctx->closed_files);
+ isc_mem_putanddetach(&pctx->mctx, pctx, sizeof(*pctx));
+ return (result);
+}
+
+void
+cfg_parser_setflags(cfg_parser_t *pctx, unsigned int flags, bool turn_on) {
+ REQUIRE(pctx != NULL);
+
+ if (turn_on) {
+ pctx->flags |= flags;
+ } else {
+ pctx->flags &= ~flags;
+ }
+}
+
+static isc_result_t
+parser_openfile(cfg_parser_t *pctx, const char *filename) {
+ isc_result_t result;
+ cfg_listelt_t *elt = NULL;
+ cfg_obj_t *stringobj = NULL;
+
+ result = isc_lex_openfile(pctx->lexer, filename);
+ if (result != ISC_R_SUCCESS) {
+ cfg_parser_error(pctx, 0, "open: %s: %s", filename,
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ CHECK(create_string(pctx, filename, &cfg_type_qstring, &stringobj));
+ CHECK(create_listelt(pctx, &elt));
+ elt->obj = stringobj;
+ ISC_LIST_APPEND(pctx->open_files->value.list, elt, link);
+
+ return (ISC_R_SUCCESS);
+cleanup:
+ CLEANUP_OBJ(stringobj);
+ return (result);
+}
+
+void
+cfg_parser_setcallback(cfg_parser_t *pctx, cfg_parsecallback_t callback,
+ void *arg) {
+ REQUIRE(pctx != NULL);
+
+ pctx->callback = callback;
+ pctx->callbackarg = arg;
+}
+
+void
+cfg_parser_reset(cfg_parser_t *pctx) {
+ REQUIRE(pctx != NULL);
+
+ if (pctx->lexer != NULL) {
+ isc_lex_close(pctx->lexer);
+ }
+
+ pctx->seen_eof = false;
+ pctx->ungotten = false;
+ pctx->errors = 0;
+ pctx->warnings = 0;
+ pctx->line = 0;
+}
+
+/*
+ * Parse a configuration using a pctx where a lexer has already
+ * been set up with a source.
+ */
+static isc_result_t
+parse2(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ result = cfg_parse_obj(pctx, type, &obj);
+
+ if (pctx->errors != 0) {
+ /* Errors have been logged. */
+ if (result == ISC_R_SUCCESS) {
+ result = ISC_R_FAILURE;
+ }
+ goto cleanup;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ /* Parsing failed but no errors have been logged. */
+ cfg_parser_error(pctx, 0, "parsing failed: %s",
+ isc_result_totext(result));
+ goto cleanup;
+ }
+
+ CHECK(parse_eof(pctx));
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+isc_result_t
+cfg_parse_file(cfg_parser_t *pctx, const char *filename, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_listelt_t *elt;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(filename != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(parser_openfile(pctx, filename));
+
+ result = parse2(pctx, type, ret);
+
+ /* Clean up the opened file */
+ elt = ISC_LIST_TAIL(pctx->open_files->value.list);
+ INSIST(elt != NULL);
+ ISC_LIST_UNLINK(pctx->open_files->value.list, elt, link);
+ ISC_LIST_APPEND(pctx->closed_files->value.list, elt, link);
+
+cleanup:
+ return (result);
+}
+
+isc_result_t
+cfg_parse_buffer(cfg_parser_t *pctx, isc_buffer_t *buffer, const char *file,
+ unsigned int line, const cfg_type_t *type, unsigned int flags,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(buffer != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+ REQUIRE((flags & ~(CFG_PCTX_NODEPRECATED)) == 0);
+
+ CHECK(isc_lex_openbuffer(pctx->lexer, buffer));
+
+ pctx->buf_name = file;
+ pctx->flags = flags;
+
+ if (line != 0U) {
+ CHECK(isc_lex_setsourceline(pctx->lexer, line));
+ }
+
+ CHECK(parse2(pctx, type, ret));
+ pctx->buf_name = NULL;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_parser_attach(cfg_parser_t *src, cfg_parser_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+void
+cfg_parser_destroy(cfg_parser_t **pctxp) {
+ cfg_parser_t *pctx;
+
+ REQUIRE(pctxp != NULL && *pctxp != NULL);
+ pctx = *pctxp;
+ *pctxp = NULL;
+
+ if (isc_refcount_decrement(&pctx->references) == 1) {
+ isc_lex_destroy(&pctx->lexer);
+ /*
+ * Cleaning up open_files does not
+ * close the files; that was already done
+ * by closing the lexer.
+ */
+ CLEANUP_OBJ(pctx->open_files);
+ CLEANUP_OBJ(pctx->closed_files);
+ isc_mem_putanddetach(&pctx->mctx, pctx, sizeof(*pctx));
+ }
+}
+
+/*
+ * void
+ */
+isc_result_t
+cfg_parse_void(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ return (cfg_create_obj(pctx, &cfg_type_void, ret));
+}
+
+void
+cfg_print_void(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ UNUSED(pctx);
+ UNUSED(obj);
+}
+
+void
+cfg_doc_void(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ UNUSED(pctx);
+ UNUSED(type);
+}
+
+bool
+cfg_obj_isvoid(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_void);
+}
+
+cfg_type_t cfg_type_void = { "void", cfg_parse_void, cfg_print_void,
+ cfg_doc_void, &cfg_rep_void, NULL };
+
+/*
+ * percentage
+ */
+isc_result_t
+cfg_parse_percentage(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ char *endp;
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ uint64_t percent;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected percentage");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ percent = strtoull(TOKEN_STRING(pctx), &endp, 10);
+ if (*endp != '%' || *(endp + 1) != 0) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected percentage");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_percentage, &obj));
+ obj->value.uint32 = (uint32_t)percent;
+ *ret = obj;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_percentage(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[64];
+ int n;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ n = snprintf(buf, sizeof(buf), "%u%%", obj->value.uint32);
+ INSIST(n > 0 && (size_t)n < sizeof(buf));
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+uint32_t
+cfg_obj_aspercentage(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_percentage);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_percentage = { "percentage", cfg_parse_percentage,
+ cfg_print_percentage, cfg_doc_terminal,
+ &cfg_rep_percentage, NULL };
+
+bool
+cfg_obj_ispercentage(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_percentage);
+}
+
+/*
+ * Fixed point
+ */
+isc_result_t
+cfg_parse_fixedpoint(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ size_t n1, n2, n3, l;
+ const char *p;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected fixed point number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ p = TOKEN_STRING(pctx);
+ l = strlen(p);
+ n1 = strspn(p, "0123456789");
+ n2 = strspn(p + n1, ".");
+ n3 = strspn(p + n1 + n2, "0123456789");
+
+ if ((n1 + n2 + n3 != l) || (n1 + n3 == 0) || n1 > 5 || n2 > 1 || n3 > 2)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected fixed point number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_fixedpoint, &obj));
+
+ obj->value.uint32 = strtoul(p, NULL, 10) * 100;
+ switch (n3) {
+ case 2:
+ obj->value.uint32 += strtoul(p + n1 + n2, NULL, 10);
+ break;
+ case 1:
+ obj->value.uint32 += strtoul(p + n1 + n2, NULL, 10) * 10;
+ break;
+ }
+ *ret = obj;
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_fixedpoint(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[64];
+ int n;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ n = snprintf(buf, sizeof(buf), "%u.%02u", obj->value.uint32 / 100,
+ obj->value.uint32 % 100);
+ INSIST(n > 0 && (size_t)n < sizeof(buf));
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+uint32_t
+cfg_obj_asfixedpoint(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_fixedpoint);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_fixedpoint = { "fixedpoint", cfg_parse_fixedpoint,
+ cfg_print_fixedpoint, cfg_doc_terminal,
+ &cfg_rep_fixedpoint, NULL };
+
+bool
+cfg_obj_isfixedpoint(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_fixedpoint);
+}
+
+/*
+ * uint32
+ */
+isc_result_t
+cfg_parse_uint32(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER | ISC_LEXOPT_CNUMBER));
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected number");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_uint32, &obj));
+
+ obj->value.uint32 = pctx->token.value.as_ulong;
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_cstr(cfg_printer_t *pctx, const char *s) {
+ cfg_print_chars(pctx, s, strlen(s));
+}
+
+void
+cfg_print_rawuint(cfg_printer_t *pctx, unsigned int u) {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%u", u);
+ cfg_print_cstr(pctx, buf);
+}
+
+void
+cfg_print_uint32(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_rawuint(pctx, obj->value.uint32);
+}
+
+bool
+cfg_obj_isuint32(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_uint32);
+}
+
+uint32_t
+cfg_obj_asuint32(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint32);
+ return (obj->value.uint32);
+}
+
+cfg_type_t cfg_type_uint32 = { "integer", cfg_parse_uint32,
+ cfg_print_uint32, cfg_doc_terminal,
+ &cfg_rep_uint32, NULL };
+
+/*
+ * uint64
+ */
+bool
+cfg_obj_isuint64(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_uint64);
+}
+
+uint64_t
+cfg_obj_asuint64(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_uint64);
+ return (obj->value.uint64);
+}
+
+void
+cfg_print_uint64(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[32];
+
+ snprintf(buf, sizeof(buf), "%" PRIu64, obj->value.uint64);
+ cfg_print_cstr(pctx, buf);
+}
+
+cfg_type_t cfg_type_uint64 = { "64_bit_integer", NULL,
+ cfg_print_uint64, cfg_doc_terminal,
+ &cfg_rep_uint64, NULL };
+
+/*
+ * Get the number of digits in a number.
+ */
+static size_t
+numlen(uint32_t num) {
+ uint32_t period = num;
+ size_t count = 0;
+
+ if (period == 0) {
+ return (1);
+ }
+ while (period > 0) {
+ count++;
+ period /= 10;
+ }
+ return (count);
+}
+
+/*
+ * duration
+ */
+void
+cfg_print_duration(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ char buf[CFG_DURATION_MAXLEN];
+ char *str;
+ const char *indicators = "YMWDHMS";
+ int count, i;
+ int durationlen[7] = { 0 };
+ isccfg_duration_t duration;
+ /*
+ * D ? The duration has a date part.
+ * T ? The duration has a time part.
+ */
+ bool D = false, T = false;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ duration = obj->value.duration;
+
+ /* If this is not an ISO 8601 duration, just print it as a number. */
+ if (!duration.iso8601) {
+ cfg_print_rawuint(pctx, duration.parts[6]);
+ return;
+ }
+
+ /* Calculate length of string. */
+ buf[0] = 'P';
+ buf[1] = '\0';
+ str = &buf[1];
+ count = 2;
+ for (i = 0; i < 6; i++) {
+ if (duration.parts[i] > 0) {
+ durationlen[i] = 1 + numlen(duration.parts[i]);
+ if (i < 4) {
+ D = true;
+ } else {
+ T = true;
+ }
+ count += durationlen[i];
+ }
+ }
+ /*
+ * Special case for seconds which is not taken into account in the
+ * above for loop: Count the length of the seconds part if it is
+ * non-zero, or if all the other parts are also zero. In the latter
+ * case this function will print "PT0S".
+ */
+ if (duration.parts[6] > 0 ||
+ (!D && !duration.parts[4] && !duration.parts[5]))
+ {
+ durationlen[6] = 1 + numlen(duration.parts[6]);
+ T = true;
+ count += durationlen[6];
+ }
+ /* Add one character for the time indicator. */
+ if (T) {
+ count++;
+ }
+ INSIST(count < CFG_DURATION_MAXLEN);
+
+ /* Now print the duration. */
+ for (i = 0; i < 6; i++) {
+ /*
+ * We don't check here if weeks and other time indicator are
+ * used mutually exclusively.
+ */
+ if (duration.parts[i] > 0) {
+ snprintf(str, durationlen[i] + 2, "%u%c",
+ (uint32_t)duration.parts[i], indicators[i]);
+ str += durationlen[i];
+ }
+ if (i == 3 && T) {
+ snprintf(str, 2, "T");
+ str += 1;
+ }
+ }
+ /* Special case for seconds. */
+ if (duration.parts[6] > 0 ||
+ (!D && !duration.parts[4] && !duration.parts[5]))
+ {
+ snprintf(str, durationlen[6] + 2, "%u%c",
+ (uint32_t)duration.parts[6], indicators[6]);
+ }
+ cfg_print_chars(pctx, buf, strlen(buf));
+}
+
+void
+cfg_print_duration_or_unlimited(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isccfg_duration_t duration;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ duration = obj->value.duration;
+
+ if (duration.unlimited) {
+ cfg_print_cstr(pctx, "unlimited");
+ } else {
+ cfg_print_duration(pctx, obj);
+ }
+}
+
+bool
+cfg_obj_isduration(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_duration);
+}
+
+uint32_t
+cfg_obj_asduration(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_duration);
+ return isccfg_duration_toseconds(&(obj->value.duration));
+}
+
+static isc_result_t
+parse_duration(cfg_parser_t *pctx, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isccfg_duration_t duration;
+
+ result = isccfg_parse_duration(&pctx->token.value.as_textregion,
+ &duration);
+
+ if (result == ISC_R_RANGE) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "duration or TTL out of range");
+ return (result);
+ } else if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj));
+ obj->value.duration = duration;
+ *ret = obj;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration or TTL value");
+ return (result);
+}
+
+isc_result_t
+cfg_parse_duration(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ return (parse_duration(pctx, ret));
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration or TTL value");
+ return (result);
+}
+
+isc_result_t
+cfg_parse_duration_or_unlimited(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isccfg_duration_t duration;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ if (strcmp(TOKEN_STRING(pctx), "unlimited") == 0) {
+ for (int i = 0; i < 7; i++) {
+ duration.parts[i] = 0;
+ }
+ duration.iso8601 = false;
+ duration.unlimited = true;
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_duration, &obj));
+ obj->value.duration = duration;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+ }
+
+ return (parse_duration(pctx, ret));
+
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected ISO 8601 duration, TTL value, or unlimited");
+ return (result);
+}
+
+/*%
+ * A duration as defined by ISO 8601 (P[n]Y[n]M[n]DT[n]H[n]M[n]S).
+ * - P is the duration indicator ("period") placed at the start.
+ * - Y is the year indicator that follows the value for the number of years.
+ * - M is the month indicator that follows the value for the number of months.
+ * - D is the day indicator that follows the value for the number of days.
+ * - T is the time indicator that precedes the time components.
+ * - H is the hour indicator that follows the value for the number of hours.
+ * - M is the minute indicator that follows the value for the number of
+ * minutes.
+ * - S is the second indicator that follows the value for the number of
+ * seconds.
+ *
+ * A duration can also be a TTL value (number + optional unit).
+ */
+cfg_type_t cfg_type_duration = { "duration", cfg_parse_duration,
+ cfg_print_duration, cfg_doc_terminal,
+ &cfg_rep_duration, NULL };
+cfg_type_t cfg_type_duration_or_unlimited = { "duration_or_unlimited",
+ cfg_parse_duration_or_unlimited,
+ cfg_print_duration_or_unlimited,
+ cfg_doc_terminal,
+ &cfg_rep_duration,
+ NULL };
+
+/*
+ * qstring (quoted string), ustring (unquoted string), astring
+ * (any string), sstring (secret string)
+ */
+
+/* Create a string object from a null-terminated C string. */
+static isc_result_t
+create_string(cfg_parser_t *pctx, const char *contents, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ int len;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ len = strlen(contents);
+ obj->value.string.length = len;
+ obj->value.string.base = isc_mem_get(pctx->mctx, len + 1);
+ if (obj->value.string.base == 0) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ return (ISC_R_NOMEMORY);
+ }
+ memmove(obj->value.string.base, contents, len);
+ obj->value.string.base[len] = '\0';
+
+ *ret = obj;
+cleanup:
+ return (result);
+}
+
+isc_result_t
+cfg_parse_qstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type != isc_tokentype_qstring) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected quoted string");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_qstring,
+ ret));
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_ustring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected unquoted string");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_ustring,
+ ret));
+cleanup:
+ return (result);
+}
+
+isc_result_t
+cfg_parse_astring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_getstringtoken(pctx));
+ return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_qstring,
+ ret));
+cleanup:
+ return (result);
+}
+
+isc_result_t
+cfg_parse_sstring(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ CHECK(cfg_getstringtoken(pctx));
+ return (create_string(pctx, TOKEN_STRING(pctx), &cfg_type_sstring,
+ ret));
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+parse_btext(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_BTEXT));
+ if (pctx->token.type != isc_tokentype_btext) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected bracketed text");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (create_string(pctx, TOKEN_STRING(pctx),
+ &cfg_type_bracketed_text, ret));
+cleanup:
+ return (result);
+}
+
+static void
+print_btext(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ /*
+ * We need to print "{" instead of running print_open()
+ * in order to preserve the exact original formatting
+ * of the bracketed text. But we increment the indent value
+ * so that print_close() will leave us back in our original
+ * state.
+ */
+ pctx->indent++;
+ cfg_print_cstr(pctx, "{");
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+ print_close(pctx);
+}
+
+static void
+doc_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "{ <unspecified-text> }");
+}
+
+bool
+cfg_is_enum(const char *s, const char *const *enums) {
+ const char *const *p;
+
+ REQUIRE(s != NULL);
+ REQUIRE(enums != NULL);
+
+ for (p = enums; *p != NULL; p++) {
+ if (strcasecmp(*p, s) == 0) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+static isc_result_t
+check_enum(cfg_parser_t *pctx, cfg_obj_t *obj, const char *const *enums) {
+ const char *s = obj->value.string.base;
+
+ if (cfg_is_enum(s, enums)) {
+ return (ISC_R_SUCCESS);
+ }
+ cfg_parser_error(pctx, 0, "'%s' unexpected", s);
+ return (ISC_R_UNEXPECTEDTOKEN);
+}
+
+isc_result_t
+cfg_parse_enum(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(parse_ustring(pctx, NULL, &obj));
+ CHECK(check_enum(pctx, obj, type->of));
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+void
+cfg_doc_enum(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const char *const *p;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "( ");
+ for (p = type->of; *p != NULL; p++) {
+ cfg_print_cstr(pctx, *p);
+ if (p[1] != NULL) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ }
+ cfg_print_cstr(pctx, " )");
+}
+
+isc_result_t
+cfg_parse_enum_or_other(cfg_parser_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype, cfg_obj_t **ret) {
+ isc_result_t result;
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string &&
+ cfg_is_enum(TOKEN_STRING(pctx), enumtype->of))
+ {
+ CHECK(cfg_parse_enum(pctx, enumtype, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, othertype, ret));
+ }
+cleanup:
+ return (result);
+}
+
+void
+cfg_doc_enum_or_other(cfg_printer_t *pctx, const cfg_type_t *enumtype,
+ const cfg_type_t *othertype) {
+ const char *const *p;
+ bool first = true;
+
+ /*
+ * If othertype is cfg_type_void, it means that enumtype is
+ * optional.
+ */
+
+ if (othertype == &cfg_type_void) {
+ cfg_print_cstr(pctx, "[ ");
+ }
+ cfg_print_cstr(pctx, "( ");
+ for (p = enumtype->of; *p != NULL; p++) {
+ if (!first) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ first = false;
+ cfg_print_cstr(pctx, *p);
+ }
+ if (othertype != &cfg_type_void) {
+ if (!first) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_doc_terminal(pctx, othertype);
+ }
+ cfg_print_cstr(pctx, " )");
+ if (othertype == &cfg_type_void) {
+ cfg_print_cstr(pctx, " ]");
+ }
+}
+
+void
+cfg_print_ustring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+}
+
+static void
+print_qstring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "\"");
+ for (size_t i = 0; i < obj->value.string.length; i++) {
+ if (obj->value.string.base[i] == '"') {
+ cfg_print_cstr(pctx, "\\");
+ }
+ cfg_print_chars(pctx, &obj->value.string.base[i], 1);
+ }
+ cfg_print_cstr(pctx, "\"");
+}
+
+static void
+print_sstring(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "\"");
+ if ((pctx->flags & CFG_PRINTER_XKEY) != 0) {
+ unsigned int len = obj->value.string.length;
+ while (len-- > 0) {
+ cfg_print_cstr(pctx, "?");
+ }
+ } else {
+ cfg_print_ustring(pctx, obj);
+ }
+ cfg_print_cstr(pctx, "\"");
+}
+
+static void
+free_string(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ isc_mem_put(pctx->mctx, obj->value.string.base,
+ obj->value.string.length + 1);
+}
+
+bool
+cfg_obj_isstring(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_string);
+}
+
+const char *
+cfg_obj_asstring(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_string);
+ return (obj->value.string.base);
+}
+
+/* Quoted string only */
+cfg_type_t cfg_type_qstring = { "quoted_string", cfg_parse_qstring,
+ print_qstring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/* Unquoted string only */
+cfg_type_t cfg_type_ustring = { "string", parse_ustring,
+ cfg_print_ustring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/* Any string (quoted or unquoted); printed with quotes */
+cfg_type_t cfg_type_astring = { "string", cfg_parse_astring,
+ print_qstring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/*
+ * Any string (quoted or unquoted); printed with quotes.
+ * If CFG_PRINTER_XKEY is set when printing the string will be '?' out.
+ */
+cfg_type_t cfg_type_sstring = { "string", cfg_parse_sstring,
+ print_sstring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/*
+ * Text enclosed in brackets. Used to pass a block of configuration
+ * text to dynamic library or external application. Checked for
+ * bracket balance, but not otherwise parsed.
+ */
+cfg_type_t cfg_type_bracketed_text = { "bracketed_text", parse_btext,
+ print_btext, doc_btext,
+ &cfg_rep_string, NULL };
+
+#if defined(HAVE_GEOIP2)
+/*
+ * "geoip" ACL element:
+ * geoip [ db <database> ] search-type <string>
+ */
+static const char *geoiptype_enums[] = {
+ "area", "areacode", "asnum", "city", "continent",
+ "country", "country3", "countryname", "domain", "isp",
+ "metro", "metrocode", "netspeed", "org", "postal",
+ "postalcode", "region", "regionname", "timezone", "tz",
+ NULL
+};
+static cfg_type_t cfg_type_geoiptype = { "geoiptype", cfg_parse_enum,
+ cfg_print_ustring, cfg_doc_enum,
+ &cfg_rep_string, &geoiptype_enums };
+
+static cfg_tuplefielddef_t geoip_fields[] = {
+ { "negated", &cfg_type_void, 0 },
+ { "db", &cfg_type_astring, 0 },
+ { "subtype", &cfg_type_geoiptype, 0 },
+ { "search", &cfg_type_astring, 0 },
+ { NULL, NULL, 0 }
+};
+
+static cfg_type_t cfg_type_geoip = { "geoip", parse_geoip, print_geoip,
+ doc_geoip, &cfg_rep_tuple, geoip_fields };
+
+static isc_result_t
+parse_geoip(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ const cfg_tuplefielddef_t *fields = type->of;
+
+ CHECK(cfg_create_tuple(pctx, type, &obj));
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[0]));
+
+ /* Parse the optional "db" field. */
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ CHECK(cfg_gettoken(pctx, 0));
+ if (strcasecmp(TOKEN_STRING(pctx), "db") == 0 &&
+ obj->value.tuple[1] == NULL)
+ {
+ CHECK(cfg_parse_obj(pctx, fields[1].type,
+ &obj->value.tuple[1]));
+ } else {
+ CHECK(cfg_parse_void(pctx, NULL, &obj->value.tuple[1]));
+ cfg_ungettoken(pctx);
+ }
+ }
+
+ CHECK(cfg_parse_obj(pctx, fields[2].type, &obj->value.tuple[2]));
+ CHECK(cfg_parse_obj(pctx, fields[3].type, &obj->value.tuple[3]));
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+print_geoip(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->value.tuple[1]->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " db ");
+ cfg_print_obj(pctx, obj->value.tuple[1]);
+ }
+ cfg_print_obj(pctx, obj->value.tuple[2]);
+ cfg_print_obj(pctx, obj->value.tuple[3]);
+}
+
+static void
+doc_geoip(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+ cfg_print_cstr(pctx, "[ db ");
+ cfg_doc_obj(pctx, &cfg_type_astring);
+ cfg_print_cstr(pctx, " ]");
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_enum(pctx, &cfg_type_geoiptype);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, &cfg_type_astring);
+}
+#endif /* HAVE_GEOIP2 */
+
+static cfg_type_t cfg_type_addrmatchelt;
+static cfg_type_t cfg_type_negated;
+
+static isc_result_t
+parse_addrmatchelt(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, CFG_LEXOPT_QSTRING));
+
+ if (pctx->token.type == isc_tokentype_string ||
+ pctx->token.type == isc_tokentype_qstring)
+ {
+ if (pctx->token.type == isc_tokentype_string &&
+ (strcasecmp(TOKEN_STRING(pctx), "key") == 0))
+ {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_keyref, ret));
+ } else if (pctx->token.type == isc_tokentype_string &&
+ (strcasecmp(TOKEN_STRING(pctx), "geoip") == 0))
+ {
+#if defined(HAVE_GEOIP2)
+ CHECK(cfg_gettoken(pctx, 0));
+ CHECK(cfg_parse_obj(pctx, &cfg_type_geoip, ret));
+#else /* if defined(HAVE_GEOIP2) */
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "'geoip' "
+ "not supported in this build");
+ return (ISC_R_UNEXPECTEDTOKEN);
+#endif /* if defined(HAVE_GEOIP2) */
+ } else {
+ if (cfg_lookingat_netaddr(
+ pctx, CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK |
+ CFG_ADDR_V6OK))
+ {
+ CHECK(cfg_parse_netprefix(pctx, NULL, ret));
+ } else {
+ CHECK(cfg_parse_astring(pctx, NULL, ret));
+ }
+ }
+ } else if (pctx->token.type == isc_tokentype_special) {
+ if (pctx->token.value.as_char == '{') {
+ /* Nested match list. */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_aml,
+ ret));
+ } else if (pctx->token.value.as_char == '!') {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "!" */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_negated, ret));
+ } else {
+ goto bad;
+ }
+ } else {
+ bad:
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP match list element");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+cleanup:
+ return (result);
+}
+
+/*%
+ * A negated address match list element (like "! 10.0.0.1").
+ * Somewhat sneakily, the caller is expected to parse the
+ * "!", but not to print it.
+ */
+static cfg_tuplefielddef_t negated_fields[] = {
+ { "negated", &cfg_type_addrmatchelt, 0 }, { NULL, NULL, 0 }
+};
+
+static void
+print_negated(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ cfg_print_cstr(pctx, "!");
+ cfg_print_tuple(pctx, obj);
+}
+
+static cfg_type_t cfg_type_negated = { "negated", cfg_parse_tuple,
+ print_negated, NULL,
+ &cfg_rep_tuple, &negated_fields };
+
+/*% An address match list element */
+
+static cfg_type_t cfg_type_addrmatchelt = { "address_match_element",
+ parse_addrmatchelt,
+ NULL,
+ cfg_doc_terminal,
+ NULL,
+ NULL };
+
+/*%
+ * A bracketed address match list
+ */
+cfg_type_t cfg_type_bracketed_aml = { "bracketed_aml",
+ cfg_parse_bracketed_list,
+ cfg_print_bracketed_list,
+ cfg_doc_bracketed_list,
+ &cfg_rep_list,
+ &cfg_type_addrmatchelt };
+
+/*
+ * Optional bracketed text
+ */
+static isc_result_t
+parse_optional_btext(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ UNUSED(type);
+
+ CHECK(cfg_peektoken(pctx, ISC_LEXOPT_BTEXT));
+ if (pctx->token.type == isc_tokentype_btext) {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_bracketed_text, ret));
+ } else {
+ CHECK(cfg_parse_obj(pctx, &cfg_type_void, ret));
+ }
+cleanup:
+ return (result);
+}
+
+static void
+print_optional_btext(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ if (obj->type == &cfg_type_void) {
+ return;
+ }
+
+ pctx->indent++;
+ cfg_print_cstr(pctx, "{");
+ cfg_print_chars(pctx, obj->value.string.base, obj->value.string.length);
+ print_close(pctx);
+}
+
+static void
+doc_optional_btext(cfg_printer_t *pctx, const cfg_type_t *type) {
+ UNUSED(type);
+
+ cfg_print_cstr(pctx, "[ { <unspecified-text> } ]");
+}
+
+cfg_type_t cfg_type_optional_bracketed_text = { "optional_btext",
+ parse_optional_btext,
+ print_optional_btext,
+ doc_optional_btext,
+ NULL,
+ NULL };
+
+/*
+ * Booleans
+ */
+
+bool
+cfg_obj_isboolean(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_boolean);
+}
+
+bool
+cfg_obj_asboolean(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_boolean);
+ return (obj->value.boolean);
+}
+
+isc_result_t
+cfg_parse_boolean(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ bool value;
+ cfg_obj_t *obj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ result = cfg_gettoken(pctx, 0);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (pctx->token.type != isc_tokentype_string) {
+ goto bad_boolean;
+ }
+
+ if ((strcasecmp(TOKEN_STRING(pctx), "true") == 0) ||
+ (strcasecmp(TOKEN_STRING(pctx), "yes") == 0) ||
+ (strcmp(TOKEN_STRING(pctx), "1") == 0))
+ {
+ value = true;
+ } else if ((strcasecmp(TOKEN_STRING(pctx), "false") == 0) ||
+ (strcasecmp(TOKEN_STRING(pctx), "no") == 0) ||
+ (strcmp(TOKEN_STRING(pctx), "0") == 0))
+ {
+ value = false;
+ } else {
+ goto bad_boolean;
+ }
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_boolean, &obj));
+ obj->value.boolean = value;
+ *ret = obj;
+ return (result);
+
+bad_boolean:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "boolean expected");
+ return (ISC_R_UNEXPECTEDTOKEN);
+
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_boolean(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ if (obj->value.boolean) {
+ cfg_print_cstr(pctx, "yes");
+ } else {
+ cfg_print_cstr(pctx, "no");
+ }
+}
+
+cfg_type_t cfg_type_boolean = { "boolean", cfg_parse_boolean,
+ cfg_print_boolean, cfg_doc_terminal,
+ &cfg_rep_boolean, NULL };
+
+/*
+ * Lists.
+ */
+
+isc_result_t
+cfg_create_list(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **obj) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(obj != NULL && *obj == NULL);
+
+ CHECK(cfg_create_obj(pctx, type, obj));
+ ISC_LIST_INIT((*obj)->value.list);
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+create_listelt(cfg_parser_t *pctx, cfg_listelt_t **eltp) {
+ cfg_listelt_t *elt;
+
+ elt = isc_mem_get(pctx->mctx, sizeof(*elt));
+ elt->obj = NULL;
+ ISC_LINK_INIT(elt, link);
+ *eltp = elt;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+free_listelt(cfg_parser_t *pctx, cfg_listelt_t *elt) {
+ if (elt->obj != NULL) {
+ cfg_obj_destroy(pctx, &elt->obj);
+ }
+ isc_mem_put(pctx->mctx, elt, sizeof(*elt));
+}
+
+static void
+free_list(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ cfg_listelt_t *elt, *next;
+ for (elt = ISC_LIST_HEAD(obj->value.list); elt != NULL; elt = next) {
+ next = ISC_LIST_NEXT(elt, link);
+ free_listelt(pctx, elt);
+ }
+}
+
+isc_result_t
+cfg_parse_listelt(cfg_parser_t *pctx, const cfg_type_t *elttype,
+ cfg_listelt_t **ret) {
+ isc_result_t result;
+ cfg_listelt_t *elt = NULL;
+ cfg_obj_t *value = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(elttype != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(create_listelt(pctx, &elt));
+
+ result = cfg_parse_obj(pctx, elttype, &value);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ elt->obj = value;
+
+ *ret = elt;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_mem_put(pctx->mctx, elt, sizeof(*elt));
+ return (result);
+}
+
+/*
+ * Parse a homogeneous list whose elements are of type 'elttype'
+ * and where each element is terminated by a semicolon.
+ */
+static isc_result_t
+parse_list(cfg_parser_t *pctx, const cfg_type_t *listtype, cfg_obj_t **ret) {
+ cfg_obj_t *listobj = NULL;
+ const cfg_type_t *listof = listtype->of;
+ isc_result_t result;
+ cfg_listelt_t *elt = NULL;
+
+ CHECK(cfg_create_list(pctx, listtype, &listobj));
+
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == /*{*/ '}')
+ {
+ break;
+ }
+ CHECK(cfg_parse_listelt(pctx, listof, &elt));
+ CHECK(parse_semicolon(pctx));
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ elt = NULL;
+ }
+ *ret = listobj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (elt != NULL) {
+ free_listelt(pctx, elt);
+ }
+ CLEANUP_OBJ(listobj);
+ return (result);
+}
+
+static void
+print_list(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_list_t *list = &obj->value.list;
+ const cfg_listelt_t *elt;
+
+ for (elt = ISC_LIST_HEAD(*list); elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) != 0) {
+ cfg_print_obj(pctx, elt->obj);
+ cfg_print_cstr(pctx, "; ");
+ } else {
+ cfg_print_indent(pctx);
+ cfg_print_obj(pctx, elt->obj);
+ cfg_print_cstr(pctx, ";\n");
+ }
+ }
+}
+
+isc_result_t
+cfg_parse_bracketed_list(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(cfg_parse_special(pctx, '{'));
+ CHECK(parse_list(pctx, type, ret));
+ CHECK(cfg_parse_special(pctx, '}'));
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_bracketed_list(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ print_open(pctx);
+ print_list(pctx, obj);
+ print_close(pctx);
+}
+
+void
+cfg_doc_bracketed_list(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "{ ");
+ cfg_doc_obj(pctx, type->of);
+ cfg_print_cstr(pctx, "; ... }");
+}
+
+/*
+ * Parse a homogeneous list whose elements are of type 'elttype'
+ * and where elements are separated by space. The list ends
+ * before the first semicolon.
+ */
+isc_result_t
+cfg_parse_spacelist(cfg_parser_t *pctx, const cfg_type_t *listtype,
+ cfg_obj_t **ret) {
+ cfg_obj_t *listobj = NULL;
+ const cfg_type_t *listof;
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(listtype != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ listof = listtype->of;
+
+ CHECK(cfg_create_list(pctx, listtype, &listobj));
+
+ for (;;) {
+ cfg_listelt_t *elt = NULL;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == ';')
+ {
+ break;
+ }
+ CHECK(cfg_parse_listelt(pctx, listof, &elt));
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ }
+ *ret = listobj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(listobj);
+ return (result);
+}
+
+void
+cfg_print_spacelist(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_list_t *list = NULL;
+ const cfg_listelt_t *elt = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ list = &obj->value.list;
+
+ for (elt = ISC_LIST_HEAD(*list); elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ cfg_print_obj(pctx, elt->obj);
+ if (ISC_LIST_NEXT(elt, link) != NULL) {
+ cfg_print_cstr(pctx, " ");
+ }
+ }
+}
+
+bool
+cfg_obj_islist(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_list);
+}
+
+const cfg_listelt_t *
+cfg_list_first(const cfg_obj_t *obj) {
+ REQUIRE(obj == NULL || obj->type->rep == &cfg_rep_list);
+ if (obj == NULL) {
+ return (NULL);
+ }
+ return (ISC_LIST_HEAD(obj->value.list));
+}
+
+const cfg_listelt_t *
+cfg_list_next(const cfg_listelt_t *elt) {
+ REQUIRE(elt != NULL);
+ return (ISC_LIST_NEXT(elt, link));
+}
+
+/*
+ * Return the length of a list object. If obj is NULL or is not
+ * a list, return 0.
+ */
+unsigned int
+cfg_list_length(const cfg_obj_t *obj, bool recurse) {
+ const cfg_listelt_t *elt;
+ unsigned int count = 0;
+
+ if (obj == NULL || !cfg_obj_islist(obj)) {
+ return (0U);
+ }
+ for (elt = cfg_list_first(obj); elt != NULL; elt = cfg_list_next(elt)) {
+ if (recurse && cfg_obj_islist(elt->obj)) {
+ count += cfg_list_length(elt->obj, recurse);
+ } else {
+ count++;
+ }
+ }
+ return (count);
+}
+
+cfg_obj_t *
+cfg_listelt_value(const cfg_listelt_t *elt) {
+ REQUIRE(elt != NULL);
+ return (elt->obj);
+}
+
+/*
+ * Maps.
+ */
+
+/*
+ * Parse a map body. That's something like
+ *
+ * "foo 1; bar { glub; }; zap true; zap false;"
+ *
+ * i.e., a sequence of option names followed by values and
+ * terminated by semicolons. Used for the top level of
+ * the named.conf syntax, as well as for the body of the
+ * options, view, zone, and other statements.
+ */
+isc_result_t
+cfg_parse_mapbody(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ const cfg_clausedef_t *const *clausesets;
+ isc_result_t result;
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+ cfg_obj_t *value = NULL;
+ cfg_obj_t *obj = NULL;
+ cfg_obj_t *eltobj = NULL;
+ cfg_obj_t *includename = NULL;
+ isc_symvalue_t symval;
+ cfg_list_t *list = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ clausesets = type->of;
+
+ CHECK(create_map(pctx, type, &obj));
+
+ obj->value.map.clausesets = clausesets;
+
+ for (;;) {
+ cfg_listelt_t *elt;
+
+ redo:
+ /*
+ * Parse the option name and see if it is known.
+ */
+ CHECK(cfg_gettoken(pctx, 0));
+
+ if (pctx->token.type != isc_tokentype_string) {
+ cfg_ungettoken(pctx);
+ break;
+ }
+
+ /*
+ * We accept "include" statements wherever a map body
+ * clause can occur.
+ */
+ if (strcasecmp(TOKEN_STRING(pctx), "include") == 0) {
+ /*
+ * Turn the file name into a temporary configuration
+ * object just so that it is not overwritten by the
+ * semicolon token.
+ */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_qstring,
+ &includename));
+ CHECK(parse_semicolon(pctx));
+
+ /* Allow include to specify a pattern that follows
+ * the same rules as the shell e.g "/path/zone*.conf" */
+ glob_t glob_obj;
+ CHECK(isc_glob(includename->value.string.base,
+ &glob_obj));
+ cfg_obj_destroy(pctx, &includename);
+
+ for (size_t i = 0; i < glob_obj.gl_pathc; ++i) {
+ CHECK(parser_openfile(pctx,
+ glob_obj.gl_pathv[i]));
+ }
+
+ isc_globfree(&glob_obj);
+
+ goto redo;
+ }
+
+ clause = NULL;
+ for (clauseset = clausesets; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL;
+ clause++)
+ {
+ if (strcasecmp(TOKEN_STRING(pctx),
+ clause->name) == 0)
+ {
+ goto done;
+ }
+ }
+ }
+ done:
+ if (clause == NULL || clause->name == NULL) {
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "unknown option");
+ /*
+ * Try to recover by parsing this option as an unknown
+ * option and discarding it.
+ */
+ CHECK(cfg_parse_obj(pctx, &cfg_type_unsupported,
+ &eltobj));
+ cfg_obj_destroy(pctx, &eltobj);
+ CHECK(parse_semicolon(pctx));
+ continue;
+ }
+
+ /* Clause is known. */
+
+ /* Issue fatal errors if appropriate */
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0) {
+ cfg_parser_error(pctx, 0,
+ "option '%s' no longer exists",
+ clause->name);
+ CHECK(ISC_R_FAILURE);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_NOTCONFIGURED) != 0) {
+ cfg_parser_error(pctx, 0,
+ "option '%s' was not "
+ "enabled at compile time",
+ clause->name);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ /* Issue warnings if appropriate */
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) == 0 &&
+ (clause->flags & CFG_CLAUSEFLAG_DEPRECATED) != 0)
+ {
+ cfg_parser_warning(pctx, 0, "option '%s' is deprecated",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is obsolete and "
+ "should be removed ",
+ clause->name);
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_EXPERIMENTAL) != 0) {
+ cfg_parser_warning(pctx, 0,
+ "option '%s' is experimental and "
+ "subject to change in the future",
+ clause->name);
+ }
+
+ /* See if the clause already has a value; if not create one. */
+ result = isc_symtab_lookup(obj->value.map.symtab, clause->name,
+ 0, &symval);
+
+ if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+ /* Multivalued clause */
+ cfg_obj_t *listobj = NULL;
+ if (result == ISC_R_NOTFOUND) {
+ CHECK(cfg_create_list(pctx,
+ &cfg_type_implicitlist,
+ &listobj));
+ symval.as_pointer = listobj;
+ result = isc_symtab_define(
+ obj->value.map.symtab, clause->name, 1,
+ symval, isc_symexists_reject);
+ if (result != ISC_R_SUCCESS) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "isc_symtab_define(%s)"
+ " "
+ "failed",
+ clause->name);
+ isc_mem_put(pctx->mctx, list,
+ sizeof(cfg_list_t));
+ goto cleanup;
+ }
+ } else {
+ INSIST(result == ISC_R_SUCCESS);
+ listobj = symval.as_pointer;
+ }
+
+ elt = NULL;
+ CHECK(cfg_parse_listelt(pctx, clause->type, &elt));
+ CHECK(parse_semicolon(pctx));
+
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ } else {
+ /* Single-valued clause */
+ if (result == ISC_R_NOTFOUND) {
+ bool callback = ((clause->flags &
+ CFG_CLAUSEFLAG_CALLBACK) !=
+ 0);
+ CHECK(parse_symtab_elt(
+ pctx, clause->name, clause->type,
+ obj->value.map.symtab, callback));
+ CHECK(parse_semicolon(pctx));
+ } else if (result == ISC_R_SUCCESS) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "'%s' redefined",
+ clause->name);
+ result = ISC_R_EXISTS;
+ goto cleanup;
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "isc_symtab_define() failed");
+ goto cleanup;
+ }
+ }
+ }
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(value);
+ CLEANUP_OBJ(obj);
+ CLEANUP_OBJ(eltobj);
+ CLEANUP_OBJ(includename);
+ return (result);
+}
+
+static isc_result_t
+parse_symtab_elt(cfg_parser_t *pctx, const char *name, cfg_type_t *elttype,
+ isc_symtab_t *symtab, bool callback) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isc_symvalue_t symval;
+
+ CHECK(cfg_parse_obj(pctx, elttype, &obj));
+
+ if (callback && pctx->callback != NULL) {
+ CHECK(pctx->callback(name, obj, pctx->callbackarg));
+ }
+
+ symval.as_pointer = obj;
+ CHECK(isc_symtab_define(symtab, name, 1, symval, isc_symexists_reject));
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+/*
+ * Parse a map; e.g., "{ foo 1; bar { glub; }; zap true; zap false; }"
+ */
+isc_result_t
+cfg_parse_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(cfg_parse_special(pctx, '{'));
+ CHECK(cfg_parse_mapbody(pctx, type, ret));
+ CHECK(cfg_parse_special(pctx, '}'));
+cleanup:
+ return (result);
+}
+
+/*
+ * Subroutine for cfg_parse_named_map() and cfg_parse_addressed_map().
+ */
+static isc_result_t
+parse_any_named_map(cfg_parser_t *pctx, cfg_type_t *nametype,
+ const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *idobj = NULL;
+ cfg_obj_t *mapobj = NULL;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(nametype != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ CHECK(cfg_parse_obj(pctx, nametype, &idobj));
+ CHECK(cfg_parse_map(pctx, type, &mapobj));
+ mapobj->value.map.id = idobj;
+ *ret = mapobj;
+ return (result);
+cleanup:
+ CLEANUP_OBJ(idobj);
+ CLEANUP_OBJ(mapobj);
+ return (result);
+}
+
+/*
+ * Parse a map identified by a string name. E.g., "name { foo 1; }".
+ * Used for the "key" and "channel" statements.
+ */
+isc_result_t
+cfg_parse_named_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_any_named_map(pctx, &cfg_type_astring, type, ret));
+}
+
+/*
+ * Parse a map identified by a network address.
+ * Used to be used for the "server" statement.
+ */
+isc_result_t
+cfg_parse_addressed_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_any_named_map(pctx, &cfg_type_netaddr, type, ret));
+}
+
+/*
+ * Parse a map identified by a network prefix.
+ * Used for the "server" statement.
+ */
+isc_result_t
+cfg_parse_netprefix_map(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ return (parse_any_named_map(pctx, &cfg_type_netprefix, type, ret));
+}
+
+static void
+print_symval(cfg_printer_t *pctx, const char *name, cfg_obj_t *obj) {
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ cfg_print_indent(pctx);
+ }
+
+ cfg_print_cstr(pctx, name);
+ cfg_print_cstr(pctx, " ");
+ cfg_print_obj(pctx, obj);
+
+ if ((pctx->flags & CFG_PRINTER_ONELINE) == 0) {
+ cfg_print_cstr(pctx, ";\n");
+ } else {
+ cfg_print_cstr(pctx, "; ");
+ }
+}
+
+void
+cfg_print_mapbody(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ for (clauseset = obj->value.map.clausesets; *clauseset != NULL;
+ clauseset++)
+ {
+ isc_symvalue_t symval;
+ const cfg_clausedef_t *clause;
+
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ isc_result_t result;
+ result = isc_symtab_lookup(obj->value.map.symtab,
+ clause->name, 0, &symval);
+ if (result == ISC_R_SUCCESS) {
+ cfg_obj_t *symobj = symval.as_pointer;
+ if (symobj->type == &cfg_type_implicitlist) {
+ /* Multivalued. */
+ cfg_list_t *list = &symobj->value.list;
+ cfg_listelt_t *elt;
+ for (elt = ISC_LIST_HEAD(*list);
+ elt != NULL;
+ elt = ISC_LIST_NEXT(elt, link))
+ {
+ print_symval(pctx, clause->name,
+ elt->obj);
+ }
+ } else {
+ /* Single-valued. */
+ print_symval(pctx, clause->name,
+ symobj);
+ }
+ } else if (result == ISC_R_NOTFOUND) {
+ /* do nothing */
+ } else {
+ UNREACHABLE();
+ }
+ }
+ }
+}
+
+static struct flagtext {
+ unsigned int flag;
+ const char *text;
+} flagtexts[] = { { CFG_CLAUSEFLAG_OBSOLETE, "obsolete" },
+ { CFG_CLAUSEFLAG_TESTONLY, "test only" },
+ { CFG_CLAUSEFLAG_NOTCONFIGURED, "not configured" },
+ { CFG_CLAUSEFLAG_MULTI, "may occur multiple times" },
+ { CFG_CLAUSEFLAG_EXPERIMENTAL, "experimental" },
+ { CFG_CLAUSEFLAG_DEPRECATED, "deprecated" },
+ { CFG_CLAUSEFLAG_ANCIENT, "ancient" },
+ { 0, NULL } };
+
+void
+cfg_print_clauseflags(cfg_printer_t *pctx, unsigned int flags) {
+ struct flagtext *p;
+ bool first = true;
+ for (p = flagtexts; p->flag != 0; p++) {
+ if ((flags & p->flag) != 0) {
+ if (first) {
+ cfg_print_cstr(pctx, " // ");
+ } else {
+ cfg_print_cstr(pctx, ", ");
+ }
+ cfg_print_cstr(pctx, p->text);
+ first = false;
+ }
+ }
+}
+
+void
+cfg_doc_mapbody(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+ (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+ {
+ continue;
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+ (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+ {
+ continue;
+ }
+ cfg_print_cstr(pctx, clause->name);
+ cfg_print_cstr(pctx, " ");
+ cfg_doc_obj(pctx, clause->type);
+ cfg_print_cstr(pctx, ";");
+ cfg_print_clauseflags(pctx, clause->flags);
+ cfg_print_cstr(pctx, "\n\n");
+ }
+ }
+}
+
+void
+cfg_print_map(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ if (obj->value.map.id != NULL) {
+ cfg_print_obj(pctx, obj->value.map.id);
+ cfg_print_cstr(pctx, " ");
+ }
+ print_open(pctx);
+ cfg_print_mapbody(pctx, obj);
+ print_close(pctx);
+}
+
+void
+cfg_doc_map(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ if (type->parse == cfg_parse_named_map) {
+ cfg_doc_obj(pctx, &cfg_type_astring);
+ cfg_print_cstr(pctx, " ");
+ } else if (type->parse == cfg_parse_addressed_map) {
+ cfg_doc_obj(pctx, &cfg_type_netaddr);
+ cfg_print_cstr(pctx, " ");
+ } else if (type->parse == cfg_parse_netprefix_map) {
+ cfg_doc_obj(pctx, &cfg_type_netprefix);
+ cfg_print_cstr(pctx, " ");
+ }
+
+ print_open(pctx);
+
+ for (clauseset = type->of; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (((pctx->flags & CFG_PRINTER_ACTIVEONLY) != 0) &&
+ (((clause->flags & CFG_CLAUSEFLAG_OBSOLETE) != 0) ||
+ ((clause->flags & CFG_CLAUSEFLAG_TESTONLY) != 0)))
+ {
+ continue;
+ }
+ if ((clause->flags & CFG_CLAUSEFLAG_ANCIENT) != 0 ||
+ (clause->flags & CFG_CLAUSEFLAG_NODOC) != 0)
+ {
+ continue;
+ }
+ cfg_print_indent(pctx);
+ cfg_print_cstr(pctx, clause->name);
+ if (clause->type->print != cfg_print_void) {
+ cfg_print_cstr(pctx, " ");
+ }
+ cfg_doc_obj(pctx, clause->type);
+ cfg_print_cstr(pctx, ";");
+ cfg_print_clauseflags(pctx, clause->flags);
+ cfg_print_cstr(pctx, "\n");
+ }
+ }
+ print_close(pctx);
+}
+
+bool
+cfg_obj_ismap(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_map);
+}
+
+isc_result_t
+cfg_map_get(const cfg_obj_t *mapobj, const char *name, const cfg_obj_t **obj) {
+ isc_result_t result;
+ isc_symvalue_t val;
+ const cfg_map_t *map;
+
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+ REQUIRE(name != NULL);
+ REQUIRE(obj != NULL && *obj == NULL);
+
+ map = &mapobj->value.map;
+
+ result = isc_symtab_lookup(map->symtab, name, MAP_SYM, &val);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ *obj = val.as_pointer;
+ return (ISC_R_SUCCESS);
+}
+
+const cfg_obj_t *
+cfg_map_getname(const cfg_obj_t *mapobj) {
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+ return (mapobj->value.map.id);
+}
+
+unsigned int
+cfg_map_count(const cfg_obj_t *mapobj) {
+ const cfg_map_t *map;
+
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+
+ map = &mapobj->value.map;
+ return (isc_symtab_count(map->symtab));
+}
+
+const char *
+cfg_map_firstclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx) {
+ cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(map != NULL && map->rep == &cfg_rep_map);
+ REQUIRE(idx != NULL);
+ REQUIRE(clauses != NULL && *clauses == NULL);
+
+ clauseset = map->of;
+ if (*clauseset == NULL) {
+ return (NULL);
+ }
+ *clauses = *clauseset;
+ *idx = 0;
+ while ((*clauseset)[*idx].name == NULL) {
+ *clauses = (*++clauseset);
+ if (*clauses == NULL) {
+ return (NULL);
+ }
+ }
+ return ((*clauseset)[*idx].name);
+}
+
+const char *
+cfg_map_nextclause(const cfg_type_t *map, const void **clauses,
+ unsigned int *idx) {
+ cfg_clausedef_t *const *clauseset;
+
+ REQUIRE(map != NULL && map->rep == &cfg_rep_map);
+ REQUIRE(idx != NULL);
+ REQUIRE(clauses != NULL && *clauses != NULL);
+
+ clauseset = map->of;
+ while (*clauseset != NULL && *clauseset != *clauses) {
+ clauseset++;
+ }
+ INSIST(*clauseset == *clauses);
+ (*idx)++;
+ while ((*clauseset)[*idx].name == NULL) {
+ *idx = 0;
+ *clauses = (*++clauseset);
+ if (*clauses == NULL) {
+ return (NULL);
+ }
+ }
+ return ((*clauseset)[*idx].name);
+}
+
+/* Parse an arbitrary token, storing its raw text representation. */
+static isc_result_t
+parse_token(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ cfg_obj_t *obj = NULL;
+ isc_result_t result;
+ isc_region_t r;
+
+ UNUSED(type);
+
+ CHECK(cfg_create_obj(pctx, &cfg_type_token, &obj));
+ CHECK(cfg_gettoken(pctx, CFG_LEXOPT_QSTRING));
+ if (pctx->token.type == isc_tokentype_eof) {
+ cfg_ungettoken(pctx);
+ result = ISC_R_EOF;
+ goto cleanup;
+ }
+
+ isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
+
+ obj->value.string.base = isc_mem_get(pctx->mctx, r.length + 1);
+ obj->value.string.length = r.length;
+ memmove(obj->value.string.base, r.base, r.length);
+ obj->value.string.base[r.length] = '\0';
+ *ret = obj;
+ return (result);
+
+cleanup:
+ if (obj != NULL) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ }
+ return (result);
+}
+
+cfg_type_t cfg_type_token = { "token", parse_token,
+ cfg_print_ustring, cfg_doc_terminal,
+ &cfg_rep_string, NULL };
+
+/*
+ * An unsupported option. This is just a list of tokens with balanced braces
+ * ending in a semicolon.
+ */
+
+static isc_result_t
+parse_unsupported(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ cfg_obj_t *listobj = NULL;
+ isc_result_t result;
+ int braces = 0;
+
+ CHECK(cfg_create_list(pctx, type, &listobj));
+
+ for (;;) {
+ cfg_listelt_t *elt = NULL;
+
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special) {
+ if (pctx->token.value.as_char == '{') {
+ braces++;
+ } else if (pctx->token.value.as_char == '}') {
+ braces--;
+ } else if (pctx->token.value.as_char == ';') {
+ if (braces == 0) {
+ break;
+ }
+ }
+ }
+ if (pctx->token.type == isc_tokentype_eof || braces < 0) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "unexpected token");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ CHECK(cfg_parse_listelt(pctx, &cfg_type_token, &elt));
+ ISC_LIST_APPEND(listobj->value.list, elt, link);
+ }
+ INSIST(braces == 0);
+ *ret = listobj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(listobj);
+ return (result);
+}
+
+cfg_type_t cfg_type_unsupported = { "unsupported", parse_unsupported,
+ cfg_print_spacelist, cfg_doc_terminal,
+ &cfg_rep_list, NULL };
+
+/*
+ * Try interpreting the current token as a network address.
+ *
+ * If CFG_ADDR_WILDOK is set in flags, "*" can be used as a wildcard
+ * and at least one of CFG_ADDR_V4OK and CFG_ADDR_V6OK must also be set. The
+ * "*" is interpreted as the IPv4 wildcard address if CFG_ADDR_V4OK is
+ * set (including the case where CFG_ADDR_V4OK and CFG_ADDR_V6OK are both set),
+ * and the IPv6 wildcard address otherwise.
+ */
+static isc_result_t
+token_addr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+ char *s;
+ struct in_addr in4a;
+ struct in6_addr in6a;
+
+ if (pctx->token.type != isc_tokentype_string) {
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+
+ s = TOKEN_STRING(pctx);
+ if ((flags & CFG_ADDR_WILDOK) != 0 && strcmp(s, "*") == 0) {
+ if ((flags & CFG_ADDR_V4OK) != 0) {
+ isc_netaddr_any(na);
+ return (ISC_R_SUCCESS);
+ } else if ((flags & CFG_ADDR_V6OK) != 0) {
+ isc_netaddr_any6(na);
+ return (ISC_R_SUCCESS);
+ } else {
+ UNREACHABLE();
+ }
+ } else {
+ if ((flags & (CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK)) != 0) {
+ if (inet_pton(AF_INET, s, &in4a) == 1) {
+ isc_netaddr_fromin(na, &in4a);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ if ((flags & CFG_ADDR_V4PREFIXOK) != 0 && strlen(s) <= 15U) {
+ char buf[64];
+ int i;
+
+ strlcpy(buf, s, sizeof(buf));
+ for (i = 0; i < 3; i++) {
+ strlcat(buf, ".0", sizeof(buf));
+ if (inet_pton(AF_INET, buf, &in4a) == 1) {
+ isc_netaddr_fromin(na, &in4a);
+ return (ISC_R_IPV4PREFIX);
+ }
+ }
+ }
+ if ((flags & CFG_ADDR_V6OK) != 0 && strlen(s) <= 127U) {
+ char buf[128]; /* see lib/bind9/getaddresses.c */
+ char *d; /* zone delimiter */
+ uint32_t zone = 0; /* scope zone ID */
+
+ strlcpy(buf, s, sizeof(buf));
+ d = strchr(buf, '%');
+ if (d != NULL) {
+ *d = '\0';
+ }
+
+ if (inet_pton(AF_INET6, buf, &in6a) == 1) {
+ if (d != NULL) {
+ isc_result_t result;
+
+ result = isc_netscope_pton(
+ AF_INET6, d + 1, &in6a, &zone);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+
+ isc_netaddr_fromin6(na, &in6a);
+ isc_netaddr_setzone(na, zone);
+ return (ISC_R_SUCCESS);
+ }
+ }
+ }
+ return (ISC_R_UNEXPECTEDTOKEN);
+}
+
+isc_result_t
+cfg_parse_rawaddr(cfg_parser_t *pctx, unsigned int flags, isc_netaddr_t *na) {
+ isc_result_t result;
+ const char *wild = "";
+ const char *prefix = "";
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(na != NULL);
+
+ CHECK(cfg_gettoken(pctx, 0));
+ result = token_addr(pctx, flags, na);
+ if (result == ISC_R_UNEXPECTEDTOKEN) {
+ if ((flags & CFG_ADDR_WILDOK) != 0) {
+ wild = " or '*'";
+ }
+ if ((flags & CFG_ADDR_V4PREFIXOK) != 0) {
+ wild = " or IPv4 prefix";
+ }
+ if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V4OK) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IPv4 address%s%s", prefix,
+ wild);
+ } else if ((flags & CFG_ADDR_MASK) == CFG_ADDR_V6OK) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IPv6 address%s%s", prefix,
+ wild);
+ } else {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected IP address%s%s", prefix,
+ wild);
+ }
+ }
+cleanup:
+ return (result);
+}
+
+bool
+cfg_lookingat_netaddr(cfg_parser_t *pctx, unsigned int flags) {
+ isc_result_t result;
+ isc_netaddr_t na_dummy;
+
+ REQUIRE(pctx != NULL);
+
+ result = token_addr(pctx, flags, &na_dummy);
+ return (result == ISC_R_SUCCESS || result == ISC_R_IPV4PREFIX);
+}
+
+isc_result_t
+cfg_parse_rawport(cfg_parser_t *pctx, unsigned int flags, in_port_t *port) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(port != NULL);
+
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
+
+ if ((flags & CFG_ADDR_WILDOK) != 0 &&
+ pctx->token.type == isc_tokentype_string &&
+ strcmp(TOKEN_STRING(pctx), "*") == 0)
+ {
+ *port = 0;
+ return (ISC_R_SUCCESS);
+ }
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected port number or '*'");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ if (pctx->token.value.as_ulong >= 65536U) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "port number out of range");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ *port = (in_port_t)(pctx->token.value.as_ulong);
+ return (ISC_R_SUCCESS);
+cleanup:
+ return (result);
+}
+
+void
+cfg_print_rawaddr(cfg_printer_t *pctx, const isc_netaddr_t *na) {
+ isc_result_t result;
+ char text[128];
+ isc_buffer_t buf;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(na != NULL);
+
+ isc_buffer_init(&buf, text, sizeof(text));
+ result = isc_netaddr_totext(na, &buf);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ cfg_print_chars(pctx, isc_buffer_base(&buf),
+ isc_buffer_usedlength(&buf));
+}
+
+/* netaddr */
+
+static unsigned int netaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK;
+static unsigned int netaddr4_flags = CFG_ADDR_V4OK;
+static unsigned int netaddr4wild_flags = CFG_ADDR_V4OK | CFG_ADDR_WILDOK;
+static unsigned int netaddr6_flags = CFG_ADDR_V6OK;
+static unsigned int netaddr6wild_flags = CFG_ADDR_V6OK | CFG_ADDR_WILDOK;
+
+static isc_result_t
+parse_netaddr(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ cfg_obj_t *obj = NULL;
+ isc_netaddr_t netaddr;
+ unsigned int flags = *(const unsigned int *)type->of;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr));
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, 0);
+ obj->value.sockaddrdscp.dscp = -1;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static void
+cfg_doc_netaddr(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp = type->of;
+ int n = 0;
+ if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) {
+ cfg_print_cstr(pctx, "( ");
+ }
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "*");
+ n++;
+ POST(n);
+ }
+ if (*flagp != CFG_ADDR_V4OK && *flagp != CFG_ADDR_V6OK) {
+ cfg_print_cstr(pctx, " )");
+ }
+}
+
+cfg_type_t cfg_type_netaddr = { "netaddr", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr_flags };
+
+cfg_type_t cfg_type_netaddr4 = { "netaddr4", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr4_flags };
+
+cfg_type_t cfg_type_netaddr4wild = { "netaddr4wild", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr4wild_flags };
+
+cfg_type_t cfg_type_netaddr6 = { "netaddr6", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr6_flags };
+
+cfg_type_t cfg_type_netaddr6wild = { "netaddr6wild", parse_netaddr,
+ cfg_print_sockaddr, cfg_doc_netaddr,
+ &cfg_rep_sockaddr, &netaddr6wild_flags };
+
+/* netprefix */
+
+isc_result_t
+cfg_parse_netprefix(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ cfg_obj_t *obj = NULL;
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ unsigned int addrlen = 0, prefixlen;
+ bool expectprefix;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ UNUSED(type);
+
+ result = cfg_parse_rawaddr(
+ pctx, CFG_ADDR_V4OK | CFG_ADDR_V4PREFIXOK | CFG_ADDR_V6OK,
+ &netaddr);
+ if (result != ISC_R_SUCCESS && result != ISC_R_IPV4PREFIX) {
+ CHECK(result);
+ }
+ switch (netaddr.family) {
+ case AF_INET:
+ addrlen = 32;
+ break;
+ case AF_INET6:
+ addrlen = 128;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ expectprefix = (result == ISC_R_IPV4PREFIX);
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_special &&
+ pctx->token.value.as_char == '/')
+ {
+ CHECK(cfg_gettoken(pctx, 0)); /* read "/" */
+ CHECK(cfg_gettoken(pctx, ISC_LEXOPT_NUMBER));
+ if (pctx->token.type != isc_tokentype_number) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "expected prefix length");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ prefixlen = pctx->token.value.as_ulong;
+ if (prefixlen > addrlen) {
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "invalid prefix length");
+ return (ISC_R_RANGE);
+ }
+ result = isc_netaddr_prefixok(&netaddr, prefixlen);
+ if (result != ISC_R_SUCCESS) {
+ char buf[ISC_NETADDR_FORMATSIZE + 1];
+ isc_netaddr_format(&netaddr, buf, sizeof(buf));
+ cfg_parser_error(pctx, CFG_LOG_NOPREP,
+ "'%s/%u': address/prefix length "
+ "mismatch",
+ buf, prefixlen);
+ return (ISC_R_FAILURE);
+ }
+ } else {
+ if (expectprefix) {
+ cfg_parser_error(pctx, CFG_LOG_NEAR,
+ "incomplete IPv4 address or prefix");
+ return (ISC_R_FAILURE);
+ }
+ prefixlen = addrlen;
+ }
+ CHECK(cfg_create_obj(pctx, &cfg_type_netprefix, &obj));
+ obj->value.netprefix.address = netaddr;
+ obj->value.netprefix.prefixlen = prefixlen;
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+cleanup:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected network prefix");
+ return (result);
+}
+
+static void
+print_netprefix(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ const cfg_netprefix_t *p = &obj->value.netprefix;
+
+ cfg_print_rawaddr(pctx, &p->address);
+ cfg_print_cstr(pctx, "/");
+ cfg_print_rawuint(pctx, p->prefixlen);
+}
+
+bool
+cfg_obj_isnetprefix(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_netprefix);
+}
+
+void
+cfg_obj_asnetprefix(const cfg_obj_t *obj, isc_netaddr_t *netaddr,
+ unsigned int *prefixlen) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_netprefix);
+ REQUIRE(netaddr != NULL);
+ REQUIRE(prefixlen != NULL);
+
+ *netaddr = obj->value.netprefix.address;
+ *prefixlen = obj->value.netprefix.prefixlen;
+}
+
+cfg_type_t cfg_type_netprefix = { "netprefix", cfg_parse_netprefix,
+ print_netprefix, cfg_doc_terminal,
+ &cfg_rep_netprefix, NULL };
+
+static isc_result_t
+parse_sockaddrsub(cfg_parser_t *pctx, const cfg_type_t *type, int flags,
+ cfg_obj_t **ret) {
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+ in_port_t port = 0;
+ cfg_obj_t *obj = NULL;
+ int have_port = 0, have_dscp = 0;
+ cfg_obj_t *dscp = NULL;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(cfg_parse_rawaddr(pctx, flags, &netaddr));
+ obj->value.sockaddrdscp.dscp = -1;
+ for (;;) {
+ CHECK(cfg_peektoken(pctx, 0));
+ if (pctx->token.type == isc_tokentype_string) {
+ if (strcasecmp(TOKEN_STRING(pctx), "port") == 0) {
+ if ((pctx->flags & CFG_PCTX_NODEPRECATED) ==
+ 0 &&
+ (flags & CFG_ADDR_PORTOK) == 0)
+ {
+ cfg_parser_warning(
+ pctx, 0,
+ "token 'port' is deprecated");
+ }
+ CHECK(cfg_gettoken(pctx, 0)); /* read "port" */
+ CHECK(cfg_parse_rawport(pctx, flags, &port));
+ ++have_port;
+ } else if ((flags & CFG_ADDR_DSCPOK) != 0 &&
+ strcasecmp(TOKEN_STRING(pctx), "dscp") == 0)
+ {
+ cfg_parser_warning(pctx, 0,
+ "'dscp' is obsolete and "
+ "should be removed");
+ CHECK(cfg_gettoken(pctx, 0)); /* read "dscp" */
+ CHECK(cfg_parse_uint32(pctx, NULL, &dscp));
+ obj->value.sockaddrdscp.dscp =
+ cfg_obj_asuint32(dscp);
+ cfg_obj_destroy(pctx, &dscp);
+ ++have_dscp;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ if (have_port > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one port");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+
+ if (have_dscp > 1) {
+ cfg_parser_error(pctx, 0, "expected at most one dscp");
+ result = ISC_R_UNEXPECTEDTOKEN;
+ goto cleanup;
+ }
+ isc_sockaddr_fromnetaddr(&obj->value.sockaddr, &netaddr, port);
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ CLEANUP_OBJ(obj);
+ return (result);
+}
+
+static unsigned int sockaddr_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_PORTOK;
+cfg_type_t cfg_type_sockaddr = { "sockaddr", cfg_parse_sockaddr,
+ cfg_print_sockaddr, cfg_doc_sockaddr,
+ &cfg_rep_sockaddr, &sockaddr_flags };
+
+static unsigned int sockaddrdscp_flags = CFG_ADDR_V4OK | CFG_ADDR_V6OK |
+ CFG_ADDR_DSCPOK | CFG_ADDR_PORTOK;
+cfg_type_t cfg_type_sockaddrdscp = { "sockaddr", cfg_parse_sockaddr,
+ cfg_print_sockaddr, cfg_doc_sockaddr,
+ &cfg_rep_sockaddr, &sockaddrdscp_flags };
+
+isc_result_t
+cfg_parse_sockaddr(cfg_parser_t *pctx, const cfg_type_t *type,
+ cfg_obj_t **ret) {
+ const unsigned int *flagp;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ flagp = type->of;
+
+ return (parse_sockaddrsub(pctx, &cfg_type_sockaddr, *flagp, ret));
+}
+
+void
+cfg_print_sockaddr(cfg_printer_t *pctx, const cfg_obj_t *obj) {
+ isc_netaddr_t netaddr;
+ in_port_t port;
+ char buf[ISC_NETADDR_FORMATSIZE];
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(obj != NULL);
+
+ isc_netaddr_fromsockaddr(&netaddr, &obj->value.sockaddr);
+ isc_netaddr_format(&netaddr, buf, sizeof(buf));
+ cfg_print_cstr(pctx, buf);
+ port = isc_sockaddr_getport(&obj->value.sockaddr);
+ if (port != 0) {
+ cfg_print_cstr(pctx, " port ");
+ cfg_print_rawuint(pctx, port);
+ }
+ if (obj->value.sockaddrdscp.dscp != -1) {
+ cfg_print_cstr(pctx, " dscp ");
+ cfg_print_rawuint(pctx, obj->value.sockaddrdscp.dscp);
+ }
+}
+
+void
+cfg_doc_sockaddr(cfg_printer_t *pctx, const cfg_type_t *type) {
+ const unsigned int *flagp;
+ int n = 0;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ flagp = type->of;
+
+ cfg_print_cstr(pctx, "( ");
+ if ((*flagp & CFG_ADDR_V4OK) != 0) {
+ cfg_print_cstr(pctx, "<ipv4_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_V6OK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "<ipv6_address>");
+ n++;
+ }
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ if (n != 0) {
+ cfg_print_cstr(pctx, " | ");
+ }
+ cfg_print_cstr(pctx, "*");
+ n++;
+ POST(n);
+ }
+ cfg_print_cstr(pctx, " ) ");
+ if ((*flagp & CFG_ADDR_PORTOK) != 0) {
+ if ((*flagp & CFG_ADDR_WILDOK) != 0) {
+ cfg_print_cstr(pctx, "[ port ( <integer> | * ) ]");
+ } else {
+ cfg_print_cstr(pctx, "[ port <integer> ]");
+ }
+ }
+}
+
+bool
+cfg_obj_issockaddr(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+ return (obj->type->rep == &cfg_rep_sockaddr);
+}
+
+const isc_sockaddr_t *
+cfg_obj_assockaddr(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL && obj->type->rep == &cfg_rep_sockaddr);
+ return (&obj->value.sockaddr);
+}
+
+isc_result_t
+cfg_gettoken(cfg_parser_t *pctx, int options) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+
+ if (pctx->seen_eof) {
+ return (ISC_R_SUCCESS);
+ }
+
+ options |= (ISC_LEXOPT_EOF | ISC_LEXOPT_NOMORE);
+
+redo:
+ pctx->token.type = isc_tokentype_unknown;
+ result = isc_lex_gettoken(pctx->lexer, options, &pctx->token);
+ pctx->ungotten = false;
+ pctx->line = isc_lex_getsourceline(pctx->lexer);
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ if (pctx->token.type == isc_tokentype_eof) {
+ result = isc_lex_close(pctx->lexer);
+ INSIST(result == ISC_R_NOMORE ||
+ result == ISC_R_SUCCESS);
+
+ if (isc_lex_getsourcename(pctx->lexer) != NULL) {
+ /*
+ * Closed an included file, not the main file.
+ */
+ cfg_listelt_t *elt;
+ elt = ISC_LIST_TAIL(
+ pctx->open_files->value.list);
+ INSIST(elt != NULL);
+ ISC_LIST_UNLINK(pctx->open_files->value.list,
+ elt, link);
+ ISC_LIST_APPEND(pctx->closed_files->value.list,
+ elt, link);
+ goto redo;
+ }
+ pctx->seen_eof = true;
+ }
+ break;
+
+ case ISC_R_NOSPACE:
+ /* More understandable than "ran out of space". */
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "token too big");
+ break;
+
+ case ISC_R_IOERROR:
+ cfg_parser_error(pctx, 0, "%s", isc_result_totext(result));
+ break;
+
+ default:
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "%s",
+ isc_result_totext(result));
+ break;
+ }
+ return (result);
+}
+
+void
+cfg_ungettoken(cfg_parser_t *pctx) {
+ REQUIRE(pctx != NULL);
+
+ if (pctx->seen_eof) {
+ return;
+ }
+ isc_lex_ungettoken(pctx->lexer, &pctx->token);
+ pctx->ungotten = true;
+}
+
+isc_result_t
+cfg_peektoken(cfg_parser_t *pctx, int options) {
+ isc_result_t result;
+
+ REQUIRE(pctx != NULL);
+
+ CHECK(cfg_gettoken(pctx, options));
+ cfg_ungettoken(pctx);
+cleanup:
+ return (result);
+}
+
+/*
+ * Get a string token, accepting both the quoted and the unquoted form.
+ * Log an error if the next token is not a string.
+ */
+static isc_result_t
+cfg_getstringtoken(cfg_parser_t *pctx) {
+ isc_result_t result;
+
+ result = cfg_gettoken(pctx, CFG_LEXOPT_QSTRING);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (pctx->token.type != isc_tokentype_string &&
+ pctx->token.type != isc_tokentype_qstring)
+ {
+ cfg_parser_error(pctx, CFG_LOG_NEAR, "expected string");
+ return (ISC_R_UNEXPECTEDTOKEN);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+void
+cfg_parser_error(cfg_parser_t *pctx, unsigned int flags, const char *fmt, ...) {
+ va_list args;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(fmt != NULL);
+
+ va_start(args, fmt);
+ parser_complain(pctx, false, flags, fmt, args);
+ va_end(args);
+ pctx->errors++;
+}
+
+void
+cfg_parser_warning(cfg_parser_t *pctx, unsigned int flags, const char *fmt,
+ ...) {
+ va_list args;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(fmt != NULL);
+
+ va_start(args, fmt);
+ parser_complain(pctx, true, flags, fmt, args);
+ va_end(args);
+ pctx->warnings++;
+}
+
+#define MAX_LOG_TOKEN 30 /* How much of a token to quote in log messages. */
+
+static bool
+have_current_file(cfg_parser_t *pctx) {
+ cfg_listelt_t *elt;
+ if (pctx->open_files == NULL) {
+ return (false);
+ }
+
+ elt = ISC_LIST_TAIL(pctx->open_files->value.list);
+ if (elt == NULL) {
+ return (false);
+ }
+
+ return (true);
+}
+
+static char *
+current_file(cfg_parser_t *pctx) {
+ static char none[] = "none";
+ cfg_listelt_t *elt;
+ cfg_obj_t *fileobj;
+
+ if (!have_current_file(pctx)) {
+ return (none);
+ }
+
+ elt = ISC_LIST_TAIL(pctx->open_files->value.list);
+ if (elt == NULL) { /* shouldn't be possible, but... */
+ return (none);
+ }
+
+ fileobj = elt->obj;
+ INSIST(fileobj->type == &cfg_type_qstring);
+ return (fileobj->value.string.base);
+}
+
+static void
+parser_complain(cfg_parser_t *pctx, bool is_warning, unsigned int flags,
+ const char *format, va_list args) {
+ char tokenbuf[MAX_LOG_TOKEN + 10];
+ static char where[PATH_MAX + 100];
+ static char message[2048];
+ int level = ISC_LOG_ERROR;
+ const char *prep = "";
+ size_t len;
+
+ if (is_warning) {
+ level = ISC_LOG_WARNING;
+ }
+
+ where[0] = '\0';
+ if (have_current_file(pctx)) {
+ snprintf(where, sizeof(where), "%s:%u: ", current_file(pctx),
+ pctx->line);
+ } else if (pctx->buf_name != NULL) {
+ snprintf(where, sizeof(where), "%s: ", pctx->buf_name);
+ }
+
+ len = vsnprintf(message, sizeof(message), format, args);
+#define ELLIPSIS " ... "
+ if (len >= sizeof(message)) {
+ message[sizeof(message) - sizeof(ELLIPSIS)] = 0;
+ strlcat(message, ELLIPSIS, sizeof(message));
+ }
+
+ if ((flags & (CFG_LOG_NEAR | CFG_LOG_BEFORE | CFG_LOG_NOPREP)) != 0) {
+ isc_region_t r;
+
+ if (pctx->ungotten) {
+ (void)cfg_gettoken(pctx, 0);
+ }
+
+ if (pctx->token.type == isc_tokentype_eof) {
+ snprintf(tokenbuf, sizeof(tokenbuf), "end of file");
+ } else if (pctx->token.type == isc_tokentype_unknown) {
+ flags = 0;
+ tokenbuf[0] = '\0';
+ } else {
+ isc_lex_getlasttokentext(pctx->lexer, &pctx->token, &r);
+ if (r.length > MAX_LOG_TOKEN) {
+ snprintf(tokenbuf, sizeof(tokenbuf),
+ "'%.*s...'", MAX_LOG_TOKEN, r.base);
+ } else {
+ snprintf(tokenbuf, sizeof(tokenbuf), "'%.*s'",
+ (int)r.length, r.base);
+ }
+ }
+
+ /* Choose a preposition. */
+ if ((flags & CFG_LOG_NEAR) != 0) {
+ prep = " near ";
+ } else if ((flags & CFG_LOG_BEFORE) != 0) {
+ prep = " before ";
+ } else {
+ prep = " ";
+ }
+ } else {
+ tokenbuf[0] = '\0';
+ }
+ isc_log_write(pctx->lctx, CAT, MOD, level, "%s%s%s%s", where, message,
+ prep, tokenbuf);
+}
+
+void
+cfg_obj_log(const cfg_obj_t *obj, isc_log_t *lctx, int level, const char *fmt,
+ ...) {
+ va_list ap;
+ char msgbuf[2048];
+
+ REQUIRE(obj != NULL);
+ REQUIRE(fmt != NULL);
+
+ if (!isc_log_wouldlog(lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ va_end(ap);
+
+ if (obj->file != NULL) {
+ isc_log_write(lctx, CAT, MOD, level, "%s:%u: %s", obj->file,
+ obj->line, msgbuf);
+ } else {
+ isc_log_write(lctx, CAT, MOD, level, "%s", msgbuf);
+ }
+}
+
+const char *
+cfg_obj_file(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+
+ return (obj->file);
+}
+
+unsigned int
+cfg_obj_line(const cfg_obj_t *obj) {
+ REQUIRE(obj != NULL);
+
+ return (obj->line);
+}
+
+isc_result_t
+cfg_create_obj(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ cfg_obj_t *obj;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+ REQUIRE(ret != NULL && *ret == NULL);
+
+ obj = isc_mem_get(pctx->mctx, sizeof(cfg_obj_t));
+
+ obj->type = type;
+ obj->file = current_file(pctx);
+ obj->line = pctx->line;
+ obj->pctx = pctx;
+
+ isc_refcount_init(&obj->references, 1);
+
+ *ret = obj;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+map_symtabitem_destroy(char *key, unsigned int type, isc_symvalue_t symval,
+ void *userarg) {
+ cfg_obj_t *obj = symval.as_pointer;
+ cfg_parser_t *pctx = (cfg_parser_t *)userarg;
+
+ UNUSED(key);
+ UNUSED(type);
+
+ cfg_obj_destroy(pctx, &obj);
+}
+
+static isc_result_t
+create_map(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) {
+ isc_result_t result;
+ isc_symtab_t *symtab = NULL;
+ cfg_obj_t *obj = NULL;
+
+ CHECK(cfg_create_obj(pctx, type, &obj));
+ CHECK(isc_symtab_create(pctx->mctx, 5, /* XXX */
+ map_symtabitem_destroy, pctx, false, &symtab));
+ obj->value.map.symtab = symtab;
+ obj->value.map.id = NULL;
+
+ *ret = obj;
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (obj != NULL) {
+ isc_mem_put(pctx->mctx, obj, sizeof(*obj));
+ }
+ return (result);
+}
+
+static void
+free_map(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ CLEANUP_OBJ(obj->value.map.id);
+ isc_symtab_destroy(&obj->value.map.symtab);
+}
+
+bool
+cfg_obj_istype(const cfg_obj_t *obj, const cfg_type_t *type) {
+ REQUIRE(obj != NULL);
+ REQUIRE(type != NULL);
+
+ return (obj->type == type);
+}
+
+/*
+ * Destroy 'obj', a configuration object created in 'pctx'.
+ */
+void
+cfg_obj_destroy(cfg_parser_t *pctx, cfg_obj_t **objp) {
+ REQUIRE(objp != NULL && *objp != NULL);
+ REQUIRE(pctx != NULL);
+
+ cfg_obj_t *obj = *objp;
+ *objp = NULL;
+
+ if (isc_refcount_decrement(&obj->references) == 1) {
+ obj->type->rep->free(pctx, obj);
+ isc_refcount_destroy(&obj->references);
+ isc_mem_put(pctx->mctx, obj, sizeof(cfg_obj_t));
+ }
+}
+
+void
+cfg_obj_attach(cfg_obj_t *src, cfg_obj_t **dest) {
+ REQUIRE(src != NULL);
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+ *dest = src;
+}
+
+static void
+free_noop(cfg_parser_t *pctx, cfg_obj_t *obj) {
+ UNUSED(pctx);
+ UNUSED(obj);
+}
+
+void
+cfg_doc_obj(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ type->doc(pctx, type);
+}
+
+void
+cfg_doc_terminal(cfg_printer_t *pctx, const cfg_type_t *type) {
+ REQUIRE(pctx != NULL);
+ REQUIRE(type != NULL);
+
+ cfg_print_cstr(pctx, "<");
+ cfg_print_cstr(pctx, type->name);
+ cfg_print_cstr(pctx, ">");
+}
+
+void
+cfg_print_grammar(const cfg_type_t *type, unsigned int flags,
+ void (*f)(void *closure, const char *text, int textlen),
+ void *closure) {
+ cfg_printer_t pctx;
+
+ pctx.f = f;
+ pctx.closure = closure;
+ pctx.indent = 0;
+ pctx.flags = flags;
+ cfg_doc_obj(&pctx, type);
+}
+
+isc_result_t
+cfg_parser_mapadd(cfg_parser_t *pctx, cfg_obj_t *mapobj, cfg_obj_t *obj,
+ const char *clausename) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_map_t *map;
+ isc_symvalue_t symval;
+ cfg_obj_t *destobj = NULL;
+ cfg_listelt_t *elt = NULL;
+ const cfg_clausedef_t *const *clauseset;
+ const cfg_clausedef_t *clause;
+
+ REQUIRE(pctx != NULL);
+ REQUIRE(mapobj != NULL && mapobj->type->rep == &cfg_rep_map);
+ REQUIRE(obj != NULL);
+ REQUIRE(clausename != NULL);
+
+ map = &mapobj->value.map;
+
+ clause = NULL;
+ for (clauseset = map->clausesets; *clauseset != NULL; clauseset++) {
+ for (clause = *clauseset; clause->name != NULL; clause++) {
+ if (strcasecmp(clause->name, clausename) == 0) {
+ goto breakout;
+ }
+ }
+ }
+
+breakout:
+ if (clause == NULL || clause->name == NULL) {
+ return (ISC_R_FAILURE);
+ }
+
+ result = isc_symtab_lookup(map->symtab, clausename, 0, &symval);
+ if (result == ISC_R_NOTFOUND) {
+ if ((clause->flags & CFG_CLAUSEFLAG_MULTI) != 0) {
+ CHECK(cfg_create_list(pctx, &cfg_type_implicitlist,
+ &destobj));
+ CHECK(create_listelt(pctx, &elt));
+ cfg_obj_attach(obj, &elt->obj);
+ ISC_LIST_APPEND(destobj->value.list, elt, link);
+ symval.as_pointer = destobj;
+ } else {
+ symval.as_pointer = obj;
+ }
+
+ CHECK(isc_symtab_define(map->symtab, clausename, 1, symval,
+ isc_symexists_reject));
+ } else {
+ cfg_obj_t *destobj2 = symval.as_pointer;
+
+ INSIST(result == ISC_R_SUCCESS);
+
+ if (destobj2->type == &cfg_type_implicitlist) {
+ CHECK(create_listelt(pctx, &elt));
+ cfg_obj_attach(obj, &elt->obj);
+ ISC_LIST_APPEND(destobj2->value.list, elt, link);
+ } else {
+ result = ISC_R_EXISTS;
+ }
+ }
+
+ destobj = NULL;
+ elt = NULL;
+
+cleanup:
+ if (elt != NULL) {
+ free_listelt(pctx, elt);
+ }
+ CLEANUP_OBJ(destobj);
+
+ return (result);
+}
+
+isc_result_t
+cfg_pluginlist_foreach(const cfg_obj_t *config, const cfg_obj_t *list,
+ isc_log_t *lctx, pluginlist_cb_t *callback,
+ void *callback_data) {
+ isc_result_t result = ISC_R_SUCCESS;
+ const cfg_listelt_t *element;
+
+ REQUIRE(config != NULL);
+ REQUIRE(callback != NULL);
+
+ for (element = cfg_list_first(list); element != NULL;
+ element = cfg_list_next(element))
+ {
+ const cfg_obj_t *plugin = cfg_listelt_value(element);
+ const cfg_obj_t *obj;
+ const char *type, *library;
+ const char *parameters = NULL;
+
+ /* Get the path to the plugin module. */
+ obj = cfg_tuple_get(plugin, "type");
+ type = cfg_obj_asstring(obj);
+
+ /* Only query plugins are supported currently. */
+ if (strcasecmp(type, "query") != 0) {
+ cfg_obj_log(obj, lctx, ISC_LOG_ERROR,
+ "unsupported plugin type");
+ return (ISC_R_FAILURE);
+ }
+
+ library = cfg_obj_asstring(cfg_tuple_get(plugin, "library"));
+
+ obj = cfg_tuple_get(plugin, "parameters");
+ if (obj != NULL && cfg_obj_isstring(obj)) {
+ parameters = cfg_obj_asstring(obj);
+ }
+
+ result = callback(config, obj, library, parameters,
+ callback_data);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ return (result);
+}
diff --git a/lib/ns/Makefile.am b/lib/ns/Makefile.am
new file mode 100644
index 0000000..eb86aeb
--- /dev/null
+++ b/lib/ns/Makefile.am
@@ -0,0 +1,57 @@
+include $(top_srcdir)/Makefile.top
+
+AM_CPPFLAGS += \
+ -DNAMED_PLUGINDIR=\"$(pkglibdir)\"
+
+lib_LTLIBRARIES = libns.la
+
+libns_ladir = $(includedir)/ns
+
+libns_la_HEADERS = \
+ include/ns/client.h \
+ include/ns/events.h \
+ include/ns/hooks.h \
+ include/ns/interfacemgr.h \
+ include/ns/listenlist.h \
+ include/ns/log.h \
+ include/ns/notify.h \
+ include/ns/query.h \
+ include/ns/server.h \
+ include/ns/sortlist.h \
+ include/ns/stats.h \
+ include/ns/types.h \
+ include/ns/update.h \
+ include/ns/xfrout.h
+
+libns_la_SOURCES = \
+ $(libns_la_HEADERS) \
+ client.c \
+ hooks.c \
+ interfacemgr.c \
+ listenlist.c \
+ log.c \
+ notify.c \
+ query.c \
+ server.c \
+ sortlist.c \
+ stats.c \
+ update.c \
+ xfrout.c
+
+libns_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBNS_CFLAGS) \
+ $(LIBUV_CFLAGS) \
+ $(OPENSSL_CFLAGS)
+
+libns_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS) \
+ $(LIBUV_LIBS) \
+ $(OPENSSL_LIBS)
+
+libns_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
diff --git a/lib/ns/Makefile.in b/lib/ns/Makefile.in
new file mode 100644
index 0000000..ba30d74
--- /dev/null
+++ b/lib/ns/Makefile.in
@@ -0,0 +1,1035 @@
+# 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
+
+subdir = lib/ns
+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 $(libns_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)$(libns_ladir)"
+LTLIBRARIES = $(lib_LTLIBRARIES)
+am__DEPENDENCIES_1 =
+libns_la_DEPENDENCIES = $(LIBDNS_LIBS) $(LIBISC_LIBS) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1)
+am__objects_1 =
+am_libns_la_OBJECTS = $(am__objects_1) libns_la-client.lo \
+ libns_la-hooks.lo libns_la-interfacemgr.lo \
+ libns_la-listenlist.lo libns_la-log.lo libns_la-notify.lo \
+ libns_la-query.lo libns_la-server.lo libns_la-sortlist.lo \
+ libns_la-stats.lo libns_la-update.lo libns_la-xfrout.lo
+libns_la_OBJECTS = $(am_libns_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 =
+libns_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(libns_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)/libns_la-client.Plo \
+ ./$(DEPDIR)/libns_la-hooks.Plo \
+ ./$(DEPDIR)/libns_la-interfacemgr.Plo \
+ ./$(DEPDIR)/libns_la-listenlist.Plo \
+ ./$(DEPDIR)/libns_la-log.Plo ./$(DEPDIR)/libns_la-notify.Plo \
+ ./$(DEPDIR)/libns_la-query.Plo ./$(DEPDIR)/libns_la-server.Plo \
+ ./$(DEPDIR)/libns_la-sortlist.Plo \
+ ./$(DEPDIR)/libns_la-stats.Plo ./$(DEPDIR)/libns_la-update.Plo \
+ ./$(DEPDIR)/libns_la-xfrout.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 = $(libns_la_SOURCES)
+DIST_SOURCES = $(libns_la_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+HEADERS = $(libns_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 -DNAMED_PLUGINDIR=\"$(pkglibdir)\"
+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 = libns.la
+libns_ladir = $(includedir)/ns
+libns_la_HEADERS = \
+ include/ns/client.h \
+ include/ns/events.h \
+ include/ns/hooks.h \
+ include/ns/interfacemgr.h \
+ include/ns/listenlist.h \
+ include/ns/log.h \
+ include/ns/notify.h \
+ include/ns/query.h \
+ include/ns/server.h \
+ include/ns/sortlist.h \
+ include/ns/stats.h \
+ include/ns/types.h \
+ include/ns/update.h \
+ include/ns/xfrout.h
+
+libns_la_SOURCES = \
+ $(libns_la_HEADERS) \
+ client.c \
+ hooks.c \
+ interfacemgr.c \
+ listenlist.c \
+ log.c \
+ notify.c \
+ query.c \
+ server.c \
+ sortlist.c \
+ stats.c \
+ update.c \
+ xfrout.c
+
+libns_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBDNS_CFLAGS) \
+ $(LIBISC_CFLAGS) \
+ $(LIBNS_CFLAGS) \
+ $(LIBUV_CFLAGS) \
+ $(OPENSSL_CFLAGS)
+
+libns_la_LIBADD = \
+ $(LIBDNS_LIBS) \
+ $(LIBISC_LIBS) \
+ $(LIBUV_LIBS) \
+ $(OPENSSL_LIBS)
+
+libns_la_LDFLAGS = \
+ $(AM_LDFLAGS) \
+ -release "$(PACKAGE_VERSION)"
+
+all: 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/ns/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/ns/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}; \
+ }
+
+libns.la: $(libns_la_OBJECTS) $(libns_la_DEPENDENCIES) $(EXTRA_libns_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(libns_la_LINK) -rpath $(libdir) $(libns_la_OBJECTS) $(libns_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-client.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-hooks.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-interfacemgr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-listenlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-log.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-notify.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-query.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-server.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-sortlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-update.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libns_la-xfrout.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 $@ $<
+
+libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-client.lo -MD -MP -MF $(DEPDIR)/libns_la-client.Tpo -c -o libns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-client.Tpo $(DEPDIR)/libns_la-client.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='client.c' object='libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-client.lo `test -f 'client.c' || echo '$(srcdir)/'`client.c
+
+libns_la-hooks.lo: hooks.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-hooks.lo -MD -MP -MF $(DEPDIR)/libns_la-hooks.Tpo -c -o libns_la-hooks.lo `test -f 'hooks.c' || echo '$(srcdir)/'`hooks.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-hooks.Tpo $(DEPDIR)/libns_la-hooks.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hooks.c' object='libns_la-hooks.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-hooks.lo `test -f 'hooks.c' || echo '$(srcdir)/'`hooks.c
+
+libns_la-interfacemgr.lo: interfacemgr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-interfacemgr.lo -MD -MP -MF $(DEPDIR)/libns_la-interfacemgr.Tpo -c -o libns_la-interfacemgr.lo `test -f 'interfacemgr.c' || echo '$(srcdir)/'`interfacemgr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-interfacemgr.Tpo $(DEPDIR)/libns_la-interfacemgr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='interfacemgr.c' object='libns_la-interfacemgr.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-interfacemgr.lo `test -f 'interfacemgr.c' || echo '$(srcdir)/'`interfacemgr.c
+
+libns_la-listenlist.lo: listenlist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-listenlist.lo -MD -MP -MF $(DEPDIR)/libns_la-listenlist.Tpo -c -o libns_la-listenlist.lo `test -f 'listenlist.c' || echo '$(srcdir)/'`listenlist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-listenlist.Tpo $(DEPDIR)/libns_la-listenlist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='listenlist.c' object='libns_la-listenlist.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-listenlist.lo `test -f 'listenlist.c' || echo '$(srcdir)/'`listenlist.c
+
+libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-log.lo -MD -MP -MF $(DEPDIR)/libns_la-log.Tpo -c -o libns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-log.Tpo $(DEPDIR)/libns_la-log.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='log.c' object='libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-log.lo `test -f 'log.c' || echo '$(srcdir)/'`log.c
+
+libns_la-notify.lo: notify.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-notify.lo -MD -MP -MF $(DEPDIR)/libns_la-notify.Tpo -c -o libns_la-notify.lo `test -f 'notify.c' || echo '$(srcdir)/'`notify.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-notify.Tpo $(DEPDIR)/libns_la-notify.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='notify.c' object='libns_la-notify.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-notify.lo `test -f 'notify.c' || echo '$(srcdir)/'`notify.c
+
+libns_la-query.lo: query.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-query.lo -MD -MP -MF $(DEPDIR)/libns_la-query.Tpo -c -o libns_la-query.lo `test -f 'query.c' || echo '$(srcdir)/'`query.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-query.Tpo $(DEPDIR)/libns_la-query.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='query.c' object='libns_la-query.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-query.lo `test -f 'query.c' || echo '$(srcdir)/'`query.c
+
+libns_la-server.lo: server.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-server.lo -MD -MP -MF $(DEPDIR)/libns_la-server.Tpo -c -o libns_la-server.lo `test -f 'server.c' || echo '$(srcdir)/'`server.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-server.Tpo $(DEPDIR)/libns_la-server.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='server.c' object='libns_la-server.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-server.lo `test -f 'server.c' || echo '$(srcdir)/'`server.c
+
+libns_la-sortlist.lo: sortlist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-sortlist.lo -MD -MP -MF $(DEPDIR)/libns_la-sortlist.Tpo -c -o libns_la-sortlist.lo `test -f 'sortlist.c' || echo '$(srcdir)/'`sortlist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-sortlist.Tpo $(DEPDIR)/libns_la-sortlist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='sortlist.c' object='libns_la-sortlist.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-sortlist.lo `test -f 'sortlist.c' || echo '$(srcdir)/'`sortlist.c
+
+libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-stats.lo -MD -MP -MF $(DEPDIR)/libns_la-stats.Tpo -c -o libns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-stats.Tpo $(DEPDIR)/libns_la-stats.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stats.c' object='libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-stats.lo `test -f 'stats.c' || echo '$(srcdir)/'`stats.c
+
+libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-update.lo -MD -MP -MF $(DEPDIR)/libns_la-update.Tpo -c -o libns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-update.Tpo $(DEPDIR)/libns_la-update.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='update.c' object='libns_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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-update.lo `test -f 'update.c' || echo '$(srcdir)/'`update.c
+
+libns_la-xfrout.lo: xfrout.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libns_la-xfrout.lo -MD -MP -MF $(DEPDIR)/libns_la-xfrout.Tpo -c -o libns_la-xfrout.lo `test -f 'xfrout.c' || echo '$(srcdir)/'`xfrout.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libns_la-xfrout.Tpo $(DEPDIR)/libns_la-xfrout.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='xfrout.c' object='libns_la-xfrout.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) $(libns_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libns_la-xfrout.lo `test -f 'xfrout.c' || echo '$(srcdir)/'`xfrout.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-libns_laHEADERS: $(libns_la_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(libns_la_HEADERS)'; test -n "$(libns_ladir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(libns_ladir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(libns_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)$(libns_ladir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(libns_ladir)" || exit $$?; \
+ done
+
+uninstall-libns_laHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(libns_la_HEADERS)'; test -n "$(libns_ladir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(libns_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: check-am
+all-am: Makefile $(LTLIBRARIES) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(libns_ladir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: 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:
+
+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."
+clean: clean-am
+
+clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/libns_la-client.Plo
+ -rm -f ./$(DEPDIR)/libns_la-hooks.Plo
+ -rm -f ./$(DEPDIR)/libns_la-interfacemgr.Plo
+ -rm -f ./$(DEPDIR)/libns_la-listenlist.Plo
+ -rm -f ./$(DEPDIR)/libns_la-log.Plo
+ -rm -f ./$(DEPDIR)/libns_la-notify.Plo
+ -rm -f ./$(DEPDIR)/libns_la-query.Plo
+ -rm -f ./$(DEPDIR)/libns_la-server.Plo
+ -rm -f ./$(DEPDIR)/libns_la-sortlist.Plo
+ -rm -f ./$(DEPDIR)/libns_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libns_la-update.Plo
+ -rm -f ./$(DEPDIR)/libns_la-xfrout.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-libns_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)/libns_la-client.Plo
+ -rm -f ./$(DEPDIR)/libns_la-hooks.Plo
+ -rm -f ./$(DEPDIR)/libns_la-interfacemgr.Plo
+ -rm -f ./$(DEPDIR)/libns_la-listenlist.Plo
+ -rm -f ./$(DEPDIR)/libns_la-log.Plo
+ -rm -f ./$(DEPDIR)/libns_la-notify.Plo
+ -rm -f ./$(DEPDIR)/libns_la-query.Plo
+ -rm -f ./$(DEPDIR)/libns_la-server.Plo
+ -rm -f ./$(DEPDIR)/libns_la-sortlist.Plo
+ -rm -f ./$(DEPDIR)/libns_la-stats.Plo
+ -rm -f ./$(DEPDIR)/libns_la-update.Plo
+ -rm -f ./$(DEPDIR)/libns_la-xfrout.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-libLTLIBRARIES uninstall-libns_laHEADERS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am 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-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-libLTLIBRARIES \
+ install-libns_laHEADERS install-man 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-libLTLIBRARIES uninstall-libns_laHEADERS unit-am \
+ unit-local
+
+.PRECIOUS: Makefile
+
+
+# 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/ns/client.c b/lib/ns/client.c
new file mode 100644
index 0000000..5ed64fd
--- /dev/null
+++ b/lib/ns/client.c
@@ -0,0 +1,3093 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <limits.h>
+#include <stdbool.h>
+
+#include <isc/aes.h>
+#include <isc/atomic.h>
+#include <isc/formatcheck.h>
+#include <isc/fuzz.h>
+#include <isc/hmac.h>
+#include <isc/log.h>
+#include <isc/mutex.h>
+#include <isc/nonce.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/safe.h>
+#include <isc/serial.h>
+#include <isc/siphash.h>
+#include <isc/stats.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/thread.h>
+#include <isc/timer.h>
+#include <isc/util.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/edns.h>
+#include <dns/events.h>
+#include <dns/message.h>
+#include <dns/peer.h>
+#include <dns/rcode.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/result.h>
+#include <dns/stats.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+
+#include <ns/client.h>
+#include <ns/interfacemgr.h>
+#include <ns/log.h>
+#include <ns/notify.h>
+#include <ns/server.h>
+#include <ns/stats.h>
+#include <ns/update.h>
+
+/***
+ *** Client
+ ***/
+
+/*! \file
+ * Client Routines
+ *
+ * Important note!
+ *
+ * All client state changes, other than that from idle to listening, occur
+ * as a result of events. This guarantees serialization and avoids the
+ * need for locking.
+ *
+ * If a routine is ever created that allows someone other than the client's
+ * task to change the client, then the client will have to be locked.
+ */
+
+#ifdef NS_CLIENT_TRACE
+#define CTRACE(m) \
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT, \
+ ISC_LOG_DEBUG(3), "%s", (m))
+#define MTRACE(m) \
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT, \
+ ISC_LOG_DEBUG(3), "clientmgr @%p: %s", manager, (m))
+#else /* ifdef NS_CLIENT_TRACE */
+#define CTRACE(m) ((void)(m))
+#define MTRACE(m) ((void)(m))
+#endif /* ifdef NS_CLIENT_TRACE */
+
+#define TCP_CLIENT(c) (((c)->attributes & NS_CLIENTATTR_TCP) != 0)
+
+#define COOKIE_SIZE 24U /* 8 + 4 + 4 + 8 */
+#define ECS_SIZE 20U /* 2 + 1 + 1 + [0..16] */
+
+#define WANTNSID(x) (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0)
+#define WANTEXPIRE(x) (((x)->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0)
+#define WANTPAD(x) (((x)->attributes & NS_CLIENTATTR_WANTPAD) != 0)
+#define USEKEEPALIVE(x) (((x)->attributes & NS_CLIENTATTR_USEKEEPALIVE) != 0)
+
+#define MANAGER_MAGIC ISC_MAGIC('N', 'S', 'C', 'm')
+#define VALID_MANAGER(m) ISC_MAGIC_VALID(m, MANAGER_MAGIC)
+
+/*
+ * Enable ns_client_dropport() by default.
+ */
+#ifndef NS_CLIENT_DROPPORT
+#define NS_CLIENT_DROPPORT 1
+#endif /* ifndef NS_CLIENT_DROPPORT */
+
+atomic_uint_fast64_t ns_client_requests = 0;
+
+static void
+clientmgr_attach(ns_clientmgr_t *source, ns_clientmgr_t **targetp);
+static void
+clientmgr_destroy(ns_clientmgr_t *manager);
+static void
+ns_client_endrequest(ns_client_t *client);
+static void
+ns_client_dumpmessage(ns_client_t *client, const char *reason);
+static void
+compute_cookie(ns_client_t *client, uint32_t when, uint32_t nonce,
+ const unsigned char *secret, isc_buffer_t *buf);
+
+void
+ns_client_recursing(ns_client_t *client) {
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(client->state == NS_CLIENTSTATE_WORKING);
+
+ LOCK(&client->manager->reclock);
+ client->state = NS_CLIENTSTATE_RECURSING;
+ ISC_LIST_APPEND(client->manager->recursing, client, rlink);
+ UNLOCK(&client->manager->reclock);
+}
+
+void
+ns_client_killoldestquery(ns_client_t *client) {
+ ns_client_t *oldest;
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ LOCK(&client->manager->reclock);
+ oldest = ISC_LIST_HEAD(client->manager->recursing);
+ if (oldest != NULL) {
+ ISC_LIST_UNLINK(client->manager->recursing, oldest, rlink);
+ ns_query_cancel(oldest);
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_reclimitdropped);
+ }
+ UNLOCK(&client->manager->reclock);
+}
+
+void
+ns_client_settimeout(ns_client_t *client, unsigned int seconds) {
+ UNUSED(client);
+ UNUSED(seconds);
+ /* XXXWPK TODO use netmgr to set timeout */
+}
+
+static void
+client_extendederror_reset(ns_client_t *client) {
+ if (client->ede == NULL) {
+ return;
+ }
+ isc_mem_put(client->mctx, client->ede->value, client->ede->length);
+ isc_mem_put(client->mctx, client->ede, sizeof(dns_ednsopt_t));
+ client->ede = NULL;
+}
+
+void
+ns_client_extendederror(ns_client_t *client, uint16_t code, const char *text) {
+ unsigned char ede[DNS_EDE_EXTRATEXT_LEN + 2];
+ isc_buffer_t buf;
+ uint16_t len = sizeof(uint16_t);
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ if (client->ede != NULL) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "already have ede, ignoring %u %s", code,
+ text == NULL ? "(null)" : text);
+ return;
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(1), "set ede: info-code %u extra-text %s",
+ code, text == NULL ? "(null)" : text);
+
+ isc_buffer_init(&buf, ede, sizeof(ede));
+ isc_buffer_putuint16(&buf, code);
+ if (text != NULL && strlen(text) > 0) {
+ if (strlen(text) < DNS_EDE_EXTRATEXT_LEN) {
+ isc_buffer_putstr(&buf, text);
+ len += (uint16_t)(strlen(text));
+ } else {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_WARNING,
+ "ede extra-text too long, ignoring");
+ }
+ }
+
+ client->ede = isc_mem_get(client->mctx, sizeof(dns_ednsopt_t));
+ client->ede->code = DNS_OPT_EDE;
+ client->ede->length = len;
+ client->ede->value = isc_mem_get(client->mctx, len);
+ memmove(client->ede->value, ede, len);
+};
+
+static void
+ns_client_endrequest(ns_client_t *client) {
+ INSIST(client->nupdates == 0);
+ INSIST(client->state == NS_CLIENTSTATE_WORKING ||
+ client->state == NS_CLIENTSTATE_RECURSING);
+
+ CTRACE("endrequest");
+
+ if (client->state == NS_CLIENTSTATE_RECURSING) {
+ LOCK(&client->manager->reclock);
+ if (ISC_LINK_LINKED(client, rlink)) {
+ ISC_LIST_UNLINK(client->manager->recursing, client,
+ rlink);
+ }
+ UNLOCK(&client->manager->reclock);
+ }
+
+ if (client->cleanup != NULL) {
+ (client->cleanup)(client);
+ client->cleanup = NULL;
+ }
+
+ if (client->view != NULL) {
+#ifdef ENABLE_AFL
+ if (client->sctx->fuzztype == isc_fuzz_resolver) {
+ dns_cache_clean(client->view->cache, INT_MAX);
+ dns_adb_flush(client->view->adb);
+ }
+#endif /* ifdef ENABLE_AFL */
+ dns_view_detach(&client->view);
+ }
+ if (client->opt != NULL) {
+ INSIST(dns_rdataset_isassociated(client->opt));
+ dns_rdataset_disassociate(client->opt);
+ dns_message_puttemprdataset(client->message, &client->opt);
+ }
+
+ client_extendederror_reset(client);
+ client->signer = NULL;
+ client->udpsize = 512;
+ client->extflags = 0;
+ client->ednsversion = -1;
+ client->additionaldepth = 0;
+ dns_ecs_init(&client->ecs);
+ dns_message_reset(client->message, DNS_MESSAGE_INTENTPARSE);
+
+ /*
+ * Clean up from recursion - normally this would be done in
+ * fetch_callback(), but if we're shutting down and canceling then
+ * it might not have happened.
+ */
+ if (client->recursionquota != NULL) {
+ isc_quota_detach(&client->recursionquota);
+ ns_stats_decrement(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ }
+
+ /*
+ * Clear all client attributes that are specific to the request
+ */
+ client->attributes = 0;
+#ifdef ENABLE_AFL
+ if (client->sctx->fuzznotify != NULL &&
+ (client->sctx->fuzztype == isc_fuzz_client ||
+ client->sctx->fuzztype == isc_fuzz_tcpclient ||
+ client->sctx->fuzztype == isc_fuzz_resolver))
+ {
+ client->sctx->fuzznotify();
+ }
+#endif /* ENABLE_AFL */
+}
+
+void
+ns_client_drop(ns_client_t *client, isc_result_t result) {
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(client->state == NS_CLIENTSTATE_WORKING ||
+ client->state == NS_CLIENTSTATE_RECURSING);
+
+ CTRACE("drop");
+ if (result != ISC_R_SUCCESS) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "request failed: %s", isc_result_totext(result));
+ }
+}
+
+static void
+client_senddone(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
+ ns_client_t *client = cbarg;
+
+ REQUIRE(client->sendhandle == handle);
+
+ CTRACE("senddone");
+
+ /*
+ * Set sendhandle to NULL, but don't detach it immediately, in
+ * case we need to retry the send. If we do resend, then
+ * sendhandle will be reattached. Whether or not we resend,
+ * we will then detach the handle from *this* send by detaching
+ * 'handle' directly below.
+ */
+ client->sendhandle = NULL;
+
+ if (result != ISC_R_SUCCESS) {
+ if (!TCP_CLIENT(client) && result == ISC_R_MAXSIZE) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "send exceeded maximum size: truncating");
+ client->query.attributes &= ~NS_QUERYATTR_ANSWERED;
+ client->rcode_override = dns_rcode_noerror;
+ ns_client_error(client, ISC_R_MAXSIZE);
+ } else {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "send failed: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ isc_nmhandle_detach(&handle);
+}
+
+static void
+client_allocsendbuf(ns_client_t *client, isc_buffer_t *buffer,
+ unsigned char **datap) {
+ unsigned char *data;
+ uint32_t bufsize;
+
+ REQUIRE(datap != NULL);
+
+ if (TCP_CLIENT(client)) {
+ INSIST(client->tcpbuf == NULL);
+ client->tcpbuf = isc_mem_get(client->manager->send_mctx,
+ NS_CLIENT_TCP_BUFFER_SIZE);
+ client->tcpbuf_size = NS_CLIENT_TCP_BUFFER_SIZE;
+ data = client->tcpbuf;
+ isc_buffer_init(buffer, data, NS_CLIENT_TCP_BUFFER_SIZE);
+ } else {
+ data = client->sendbuf;
+ if ((client->attributes & NS_CLIENTATTR_HAVECOOKIE) == 0) {
+ if (client->view != NULL) {
+ bufsize = client->view->nocookieudp;
+ } else {
+ bufsize = 512;
+ }
+ } else {
+ bufsize = client->udpsize;
+ }
+ if (bufsize > client->udpsize) {
+ bufsize = client->udpsize;
+ }
+ if (bufsize > NS_CLIENT_SEND_BUFFER_SIZE) {
+ bufsize = NS_CLIENT_SEND_BUFFER_SIZE;
+ }
+ isc_buffer_init(buffer, data, bufsize);
+ }
+ *datap = data;
+}
+
+static void
+client_sendpkg(ns_client_t *client, isc_buffer_t *buffer) {
+ isc_result_t result;
+ isc_region_t r;
+ dns_ttl_t min_ttl = 0;
+
+ REQUIRE(client->sendhandle == NULL);
+
+ if (isc_buffer_base(buffer) == client->tcpbuf) {
+ size_t used = isc_buffer_usedlength(buffer);
+ client->tcpbuf = isc_mem_reget(client->manager->send_mctx,
+ client->tcpbuf,
+ client->tcpbuf_size, used);
+ client->tcpbuf_size = used;
+ r.base = client->tcpbuf;
+ r.length = used;
+ } else {
+ isc_buffer_usedregion(buffer, &r);
+ }
+ isc_nmhandle_attach(client->handle, &client->sendhandle);
+
+ if (isc_nm_is_http_handle(client->handle)) {
+ result = dns_message_response_minttl(client->message, &min_ttl);
+ if (result == ISC_R_SUCCESS) {
+ isc_nm_set_maxage(client->handle, min_ttl);
+ }
+ }
+ isc_nm_send(client->handle, &r, client_senddone, client);
+}
+
+void
+ns_client_sendraw(ns_client_t *client, dns_message_t *message) {
+ isc_result_t result;
+ unsigned char *data = NULL;
+ isc_buffer_t buffer;
+ isc_region_t r;
+ isc_region_t *mr = NULL;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ CTRACE("sendraw");
+
+ mr = dns_message_getrawmessage(message);
+ if (mr == NULL) {
+ result = ISC_R_UNEXPECTEDEND;
+ goto done;
+ }
+
+ client_allocsendbuf(client, &buffer, &data);
+
+ if (mr->length > isc_buffer_length(&buffer)) {
+ result = ISC_R_NOSPACE;
+ goto done;
+ }
+
+ /*
+ * Copy message to buffer and fixup id.
+ */
+ isc_buffer_availableregion(&buffer, &r);
+ result = isc_buffer_copyregion(&buffer, mr);
+ if (result != ISC_R_SUCCESS) {
+ goto done;
+ }
+ r.base[0] = (client->message->id >> 8) & 0xff;
+ r.base[1] = client->message->id & 0xff;
+
+#ifdef HAVE_DNSTAP
+ if (client->view != NULL) {
+ bool tcp = TCP_CLIENT(client);
+ dns_dtmsgtype_t dtmsgtype;
+ if (client->message->opcode == dns_opcode_update) {
+ dtmsgtype = DNS_DTTYPE_UR;
+ } else if ((client->message->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_CR;
+ } else {
+ dtmsgtype = DNS_DTTYPE_AR;
+ }
+ dns_dt_send(client->view, dtmsgtype, &client->peeraddr,
+ &client->destsockaddr, tcp, NULL,
+ &client->requesttime, NULL, &buffer);
+ }
+#endif
+
+ client_sendpkg(client, &buffer);
+
+ return;
+done:
+ if (client->tcpbuf != NULL) {
+ isc_mem_put(client->manager->send_mctx, client->tcpbuf,
+ client->tcpbuf_size);
+ }
+
+ ns_client_drop(client, result);
+}
+
+void
+ns_client_send(ns_client_t *client) {
+ isc_result_t result;
+ unsigned char *data = NULL;
+ isc_buffer_t buffer = { .magic = 0 };
+ isc_region_t r;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ unsigned int render_opts;
+ unsigned int preferred_glue;
+ bool opt_included = false;
+ size_t respsize;
+ dns_aclenv_t *env = NULL;
+#ifdef HAVE_DNSTAP
+ unsigned char zone[DNS_NAME_MAXWIRE];
+ dns_dtmsgtype_t dtmsgtype;
+ isc_region_t zr;
+#endif /* HAVE_DNSTAP */
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ if ((client->query.attributes & NS_QUERYATTR_ANSWERED) != 0) {
+ return;
+ }
+
+ /*
+ * XXXWPK TODO
+ * Delay the response according to the -T delay option
+ */
+
+ env = client->manager->aclenv;
+
+ CTRACE("send");
+
+ if (client->message->opcode == dns_opcode_query &&
+ (client->attributes & NS_CLIENTATTR_RA) != 0)
+ {
+ client->message->flags |= DNS_MESSAGEFLAG_RA;
+ }
+
+ if ((client->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0) {
+ render_opts = 0;
+ } else {
+ render_opts = DNS_MESSAGERENDER_OMITDNSSEC;
+ }
+
+ preferred_glue = 0;
+ if (client->view != NULL) {
+ if (client->view->preferred_glue == dns_rdatatype_a) {
+ preferred_glue = DNS_MESSAGERENDER_PREFER_A;
+ } else if (client->view->preferred_glue == dns_rdatatype_aaaa) {
+ preferred_glue = DNS_MESSAGERENDER_PREFER_AAAA;
+ }
+ }
+ if (preferred_glue == 0) {
+ if (isc_sockaddr_pf(&client->peeraddr) == AF_INET) {
+ preferred_glue = DNS_MESSAGERENDER_PREFER_A;
+ } else {
+ preferred_glue = DNS_MESSAGERENDER_PREFER_AAAA;
+ }
+ }
+
+ /*
+ * Create an OPT for our reply.
+ */
+ if ((client->attributes & NS_CLIENTATTR_WANTOPT) != 0) {
+ result = ns_client_addopt(client, client->message,
+ &client->opt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+
+ client_allocsendbuf(client, &buffer, &data);
+
+ result = dns_compress_init(&cctx, -1, client->mctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ if (client->peeraddr_valid && client->view != NULL) {
+ isc_netaddr_t netaddr;
+ dns_name_t *name = NULL;
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ if (client->message->tsigkey != NULL) {
+ name = &client->message->tsigkey->name;
+ }
+
+ if (client->view->nocasecompress == NULL ||
+ !dns_acl_allowed(&netaddr, name,
+ client->view->nocasecompress, env))
+ {
+ dns_compress_setsensitive(&cctx, true);
+ }
+
+ if (!client->view->msgcompression) {
+ dns_compress_disable(&cctx);
+ }
+ }
+ cleanup_cctx = true;
+
+ result = dns_message_renderbegin(client->message, &cctx, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ if (client->opt != NULL) {
+ result = dns_message_setopt(client->message, client->opt);
+ opt_included = true;
+ client->opt = NULL;
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ result = dns_message_rendersection(client->message,
+ DNS_SECTION_QUESTION, 0);
+ if (result == ISC_R_NOSPACE) {
+ client->message->flags |= DNS_MESSAGEFLAG_TC;
+ goto renderend;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * Stop after the question if TC was set for rate limiting.
+ */
+ if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ goto renderend;
+ }
+ result = dns_message_rendersection(client->message, DNS_SECTION_ANSWER,
+ DNS_MESSAGERENDER_PARTIAL |
+ render_opts);
+ if (result == ISC_R_NOSPACE) {
+ client->message->flags |= DNS_MESSAGEFLAG_TC;
+ goto renderend;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(
+ client->message, DNS_SECTION_AUTHORITY,
+ DNS_MESSAGERENDER_PARTIAL | render_opts);
+ if (result == ISC_R_NOSPACE) {
+ client->message->flags |= DNS_MESSAGEFLAG_TC;
+ goto renderend;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_rendersection(client->message,
+ DNS_SECTION_ADDITIONAL,
+ preferred_glue | render_opts);
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOSPACE) {
+ goto cleanup;
+ }
+renderend:
+ result = dns_message_renderend(client->message);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+#ifdef HAVE_DNSTAP
+ memset(&zr, 0, sizeof(zr));
+ if (((client->message->flags & DNS_MESSAGEFLAG_AA) != 0) &&
+ (client->query.authzone != NULL))
+ {
+ isc_result_t eresult;
+ isc_buffer_t b;
+ dns_name_t *zo = dns_zone_getorigin(client->query.authzone);
+
+ isc_buffer_init(&b, zone, sizeof(zone));
+ dns_compress_setmethods(&cctx, DNS_COMPRESS_NONE);
+ eresult = dns_name_towire(zo, &cctx, &b);
+ if (eresult == ISC_R_SUCCESS) {
+ isc_buffer_usedregion(&b, &zr);
+ }
+ }
+
+ if (client->message->opcode == dns_opcode_update) {
+ dtmsgtype = DNS_DTTYPE_UR;
+ } else if ((client->message->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_CR;
+ } else {
+ dtmsgtype = DNS_DTTYPE_AR;
+ }
+#endif /* HAVE_DNSTAP */
+
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+
+ if (client->sendcb != NULL) {
+ client->sendcb(&buffer);
+ } else if (TCP_CLIENT(client)) {
+ isc_buffer_usedregion(&buffer, &r);
+#ifdef HAVE_DNSTAP
+ if (client->view != NULL) {
+ dns_dt_send(client->view, dtmsgtype, &client->peeraddr,
+ &client->destsockaddr, true, &zr,
+ &client->requesttime, NULL, &buffer);
+ }
+#endif /* HAVE_DNSTAP */
+
+ respsize = isc_buffer_usedlength(&buffer);
+
+ client_sendpkg(client, &buffer);
+
+ switch (isc_sockaddr_pf(&client->peeraddr)) {
+ case AF_INET:
+ isc_stats_increment(client->sctx->tcpoutstats4,
+ ISC_MIN((int)respsize / 16, 256));
+ break;
+ case AF_INET6:
+ isc_stats_increment(client->sctx->tcpoutstats6,
+ ISC_MIN((int)respsize / 16, 256));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+#ifdef HAVE_DNSTAP
+ /*
+ * Log dnstap data first, because client_sendpkg() may
+ * leave client->view set to NULL.
+ */
+ if (client->view != NULL) {
+ dns_dt_send(client->view, dtmsgtype, &client->peeraddr,
+ &client->destsockaddr, false, &zr,
+ &client->requesttime, NULL, &buffer);
+ }
+#endif /* HAVE_DNSTAP */
+
+ respsize = isc_buffer_usedlength(&buffer);
+
+ client_sendpkg(client, &buffer);
+
+ switch (isc_sockaddr_pf(&client->peeraddr)) {
+ case AF_INET:
+ isc_stats_increment(client->sctx->udpoutstats4,
+ ISC_MIN((int)respsize / 16, 256));
+ break;
+ case AF_INET6:
+ isc_stats_increment(client->sctx->udpoutstats6,
+ ISC_MIN((int)respsize / 16, 256));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ /* update statistics (XXXJT: is it okay to access message->xxxkey?) */
+ ns_stats_increment(client->sctx->nsstats, ns_statscounter_response);
+
+ dns_rcodestats_increment(client->sctx->rcodestats,
+ client->message->rcode);
+ if (opt_included) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_edns0out);
+ }
+ if (client->message->tsigkey != NULL) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_tsigout);
+ }
+ if (client->message->sig0key != NULL) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_sig0out);
+ }
+ if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_truncatedresp);
+ }
+
+ client->query.attributes |= NS_QUERYATTR_ANSWERED;
+
+ return;
+
+cleanup:
+ if (client->tcpbuf != NULL) {
+ isc_mem_put(client->manager->send_mctx, client->tcpbuf,
+ client->tcpbuf_size);
+ }
+
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+}
+
+#if NS_CLIENT_DROPPORT
+#define DROPPORT_NO 0
+#define DROPPORT_REQUEST 1
+#define DROPPORT_RESPONSE 2
+/*%
+ * ns_client_dropport determines if certain requests / responses
+ * should be dropped based on the port number.
+ *
+ * Returns:
+ * \li 0: Don't drop.
+ * \li 1: Drop request.
+ * \li 2: Drop (error) response.
+ */
+static int
+ns_client_dropport(in_port_t port) {
+ switch (port) {
+ case 7: /* echo */
+ case 13: /* daytime */
+ case 19: /* chargen */
+ case 37: /* time */
+ return (DROPPORT_REQUEST);
+ case 464: /* kpasswd */
+ return (DROPPORT_RESPONSE);
+ }
+ return (DROPPORT_NO);
+}
+#endif /* if NS_CLIENT_DROPPORT */
+
+void
+ns_client_error(ns_client_t *client, isc_result_t result) {
+ dns_message_t *message = NULL;
+ dns_rcode_t rcode;
+ bool trunc = false;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ CTRACE("error");
+
+ message = client->message;
+
+ if (client->rcode_override == -1) {
+ rcode = dns_result_torcode(result);
+ } else {
+ rcode = (dns_rcode_t)(client->rcode_override & 0xfff);
+ }
+
+ if (result == ISC_R_MAXSIZE) {
+ trunc = true;
+ }
+
+#if NS_CLIENT_DROPPORT
+ /*
+ * Don't send FORMERR to ports on the drop port list.
+ */
+ if (rcode == dns_rcode_formerr &&
+ ns_client_dropport(isc_sockaddr_getport(&client->peeraddr)) !=
+ DROPPORT_NO)
+ {
+ char buf[64];
+ isc_buffer_t b;
+
+ isc_buffer_init(&b, buf, sizeof(buf) - 1);
+ if (dns_rcode_totext(rcode, &b) != ISC_R_SUCCESS) {
+ isc_buffer_putstr(&b, "UNKNOWN RCODE");
+ }
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(10),
+ "dropped error (%.*s) response: suspicious port",
+ (int)isc_buffer_usedlength(&b), buf);
+ ns_client_drop(client, ISC_R_SUCCESS);
+ return;
+ }
+#endif /* if NS_CLIENT_DROPPORT */
+
+ /*
+ * Try to rate limit error responses.
+ */
+ if (client->view != NULL && client->view->rrl != NULL) {
+ bool wouldlog;
+ char log_buf[DNS_RRL_LOG_BUF_LEN];
+ dns_rrl_result_t rrl_result;
+ int loglevel;
+
+ if ((client->sctx->options & NS_SERVER_LOGQUERIES) != 0) {
+ loglevel = DNS_RRL_LOG_DROP;
+ } else {
+ loglevel = ISC_LOG_DEBUG(1);
+ }
+ wouldlog = isc_log_wouldlog(ns_lctx, loglevel);
+ rrl_result = dns_rrl(client->view, NULL, &client->peeraddr,
+ TCP_CLIENT(client), dns_rdataclass_in,
+ dns_rdatatype_none, NULL, result,
+ client->now, wouldlog, log_buf,
+ sizeof(log_buf));
+ if (rrl_result != DNS_RRL_RESULT_OK) {
+ /*
+ * Log dropped errors in the query category
+ * so that they are not lost in silence.
+ * Starts of rate-limited bursts are logged in
+ * NS_LOGCATEGORY_RRL.
+ */
+ if (wouldlog) {
+ ns_client_log(client,
+ NS_LOGCATEGORY_QUERY_ERRORS,
+ NS_LOGMODULE_CLIENT, loglevel,
+ "%s", log_buf);
+ }
+ /*
+ * Some error responses cannot be 'slipped',
+ * so don't try to slip any error responses.
+ */
+ if (!client->view->rrl->log_only) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_ratedropped);
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_dropped);
+ ns_client_drop(client, DNS_R_DROP);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Message may be an in-progress reply that we had trouble
+ * with, in which case QR will be set. We need to clear QR before
+ * calling dns_message_reply() to avoid triggering an assertion.
+ */
+ message->flags &= ~DNS_MESSAGEFLAG_QR;
+ /*
+ * AA and AD shouldn't be set.
+ */
+ message->flags &= ~(DNS_MESSAGEFLAG_AA | DNS_MESSAGEFLAG_AD);
+ result = dns_message_reply(message, true);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * It could be that we've got a query with a good header,
+ * but a bad question section, so we try again with
+ * want_question_section set to false.
+ */
+ result = dns_message_reply(message, false);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_drop(client, result);
+ return;
+ }
+ }
+
+ message->rcode = rcode;
+ if (trunc) {
+ message->flags |= DNS_MESSAGEFLAG_TC;
+ }
+
+ if (rcode == dns_rcode_formerr) {
+ /*
+ * FORMERR loop avoidance: If we sent a FORMERR message
+ * with the same ID to the same client less than two
+ * seconds ago, assume that we are in an infinite error
+ * packet dialog with a server for some protocol whose
+ * error responses look enough like DNS queries to
+ * elicit a FORMERR response. Drop a packet to break
+ * the loop.
+ */
+ if (isc_sockaddr_equal(&client->peeraddr,
+ &client->formerrcache.addr) &&
+ message->id == client->formerrcache.id &&
+ (isc_time_seconds(&client->requesttime) -
+ client->formerrcache.time) < 2)
+ {
+ /* Drop packet. */
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "possible error packet loop, "
+ "FORMERR dropped");
+ ns_client_drop(client, result);
+ return;
+ }
+ client->formerrcache.addr = client->peeraddr;
+ client->formerrcache.time =
+ isc_time_seconds(&client->requesttime);
+ client->formerrcache.id = message->id;
+ } else if (rcode == dns_rcode_servfail && client->query.qname != NULL &&
+ client->view != NULL && client->view->fail_ttl != 0 &&
+ ((client->attributes & NS_CLIENTATTR_NOSETFC) == 0))
+ {
+ /*
+ * SERVFAIL caching: store qname/qtype of failed queries
+ */
+ isc_time_t expire;
+ isc_interval_t i;
+ uint32_t flags = 0;
+
+ if ((message->flags & DNS_MESSAGEFLAG_CD) != 0) {
+ flags = NS_FAILCACHE_CD;
+ }
+
+ isc_interval_set(&i, client->view->fail_ttl, 0);
+ result = isc_time_nowplusinterval(&expire, &i);
+ if (result == ISC_R_SUCCESS) {
+ dns_badcache_add(
+ client->view->failcache, client->query.qname,
+ client->query.qtype, true, flags, &expire);
+ }
+ }
+
+ ns_client_send(client);
+}
+
+isc_result_t
+ns_client_addopt(ns_client_t *client, dns_message_t *message,
+ dns_rdataset_t **opt) {
+ unsigned char ecs[ECS_SIZE];
+ char nsid[_POSIX_HOST_NAME_MAX + 1], *nsidp = NULL;
+ unsigned char cookie[COOKIE_SIZE];
+ isc_result_t result;
+ dns_view_t *view = NULL;
+ dns_resolver_t *resolver = NULL;
+ uint16_t udpsize;
+ dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
+ int count = 0;
+ unsigned int flags;
+ unsigned char expire[4];
+ unsigned char advtimo[2];
+ dns_aclenv_t *env = NULL;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(opt != NULL && *opt == NULL);
+ REQUIRE(message != NULL);
+
+ env = client->manager->aclenv;
+ view = client->view;
+ resolver = (view != NULL) ? view->resolver : NULL;
+ if (resolver != NULL) {
+ udpsize = dns_resolver_getudpsize(resolver);
+ } else {
+ udpsize = client->sctx->udpsize;
+ }
+
+ flags = client->extflags & DNS_MESSAGEEXTFLAG_REPLYPRESERVE;
+
+ /* Set EDNS options if applicable */
+ if (WANTNSID(client)) {
+ if (client->sctx->server_id != NULL) {
+ nsidp = client->sctx->server_id;
+ } else if (client->sctx->usehostname) {
+ result = gethostname(nsid, sizeof(nsid));
+ if (result != ISC_R_SUCCESS) {
+ goto no_nsid;
+ }
+ nsidp = nsid;
+ } else {
+ goto no_nsid;
+ }
+
+ INSIST(count < DNS_EDNSOPTIONS);
+ ednsopts[count].code = DNS_OPT_NSID;
+ ednsopts[count].length = (uint16_t)strlen(nsidp);
+ ednsopts[count].value = (unsigned char *)nsidp;
+ count++;
+ }
+no_nsid:
+ if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0) {
+ isc_buffer_t buf;
+ isc_stdtime_t now;
+ uint32_t nonce;
+
+ isc_buffer_init(&buf, cookie, sizeof(cookie));
+ isc_stdtime_get(&now);
+
+ isc_random_buf(&nonce, sizeof(nonce));
+
+ compute_cookie(client, now, nonce, client->sctx->secret, &buf);
+
+ INSIST(count < DNS_EDNSOPTIONS);
+ ednsopts[count].code = DNS_OPT_COOKIE;
+ ednsopts[count].length = COOKIE_SIZE;
+ ednsopts[count].value = cookie;
+ count++;
+ }
+ if ((client->attributes & NS_CLIENTATTR_HAVEEXPIRE) != 0) {
+ isc_buffer_t buf;
+
+ INSIST(count < DNS_EDNSOPTIONS);
+
+ isc_buffer_init(&buf, expire, sizeof(expire));
+ isc_buffer_putuint32(&buf, client->expire);
+ ednsopts[count].code = DNS_OPT_EXPIRE;
+ ednsopts[count].length = 4;
+ ednsopts[count].value = expire;
+ count++;
+ }
+ if (((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) &&
+ (client->ecs.addr.family == AF_INET ||
+ client->ecs.addr.family == AF_INET6 ||
+ client->ecs.addr.family == AF_UNSPEC))
+ {
+ isc_buffer_t buf;
+ uint8_t addr[16];
+ uint32_t plen, addrl;
+ uint16_t family = 0;
+
+ /* Add CLIENT-SUBNET option. */
+
+ plen = client->ecs.source;
+
+ /* Round up prefix len to a multiple of 8 */
+ addrl = (plen + 7) / 8;
+
+ switch (client->ecs.addr.family) {
+ case AF_UNSPEC:
+ INSIST(plen == 0);
+ family = 0;
+ break;
+ case AF_INET:
+ INSIST(plen <= 32);
+ family = 1;
+ memmove(addr, &client->ecs.addr.type, addrl);
+ break;
+ case AF_INET6:
+ INSIST(plen <= 128);
+ family = 2;
+ memmove(addr, &client->ecs.addr.type, addrl);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_buffer_init(&buf, ecs, sizeof(ecs));
+ /* family */
+ isc_buffer_putuint16(&buf, family);
+ /* source prefix-length */
+ isc_buffer_putuint8(&buf, client->ecs.source);
+ /* scope prefix-length */
+ isc_buffer_putuint8(&buf, client->ecs.scope);
+
+ /* address */
+ if (addrl > 0) {
+ /* Mask off last address byte */
+ if ((plen % 8) != 0) {
+ addr[addrl - 1] &= ~0U << (8 - (plen % 8));
+ }
+ isc_buffer_putmem(&buf, addr, (unsigned)addrl);
+ }
+
+ ednsopts[count].code = DNS_OPT_CLIENT_SUBNET;
+ ednsopts[count].length = addrl + 4;
+ ednsopts[count].value = ecs;
+ count++;
+ }
+ if (TCP_CLIENT(client) && USEKEEPALIVE(client)) {
+ isc_buffer_t buf;
+ uint32_t adv;
+
+ INSIST(count < DNS_EDNSOPTIONS);
+
+ isc_nm_gettimeouts(isc_nmhandle_netmgr(client->handle), NULL,
+ NULL, NULL, &adv);
+ adv /= 100; /* units of 100 milliseconds */
+ isc_buffer_init(&buf, advtimo, sizeof(advtimo));
+ isc_buffer_putuint16(&buf, (uint16_t)adv);
+ ednsopts[count].code = DNS_OPT_TCP_KEEPALIVE;
+ ednsopts[count].length = 2;
+ ednsopts[count].value = advtimo;
+ count++;
+ }
+
+ if (client->ede != NULL) {
+ INSIST(count < DNS_EDNSOPTIONS);
+ ednsopts[count].code = DNS_OPT_EDE;
+ ednsopts[count].length = client->ede->length;
+ ednsopts[count].value = client->ede->value;
+ count++;
+ }
+
+ /* Padding must be added last */
+ if ((view != NULL) && (view->padding > 0) && WANTPAD(client) &&
+ (TCP_CLIENT(client) ||
+ ((client->attributes & NS_CLIENTATTR_HAVECOOKIE) != 0)))
+ {
+ isc_netaddr_t netaddr;
+ int match;
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ result = dns_acl_match(&netaddr, NULL, view->pad_acl, env,
+ &match, NULL);
+ if (result == ISC_R_SUCCESS && match > 0) {
+ INSIST(count < DNS_EDNSOPTIONS);
+
+ ednsopts[count].code = DNS_OPT_PAD;
+ ednsopts[count].length = 0;
+ ednsopts[count].value = NULL;
+ count++;
+
+ dns_message_setpadding(message, view->padding);
+ }
+ }
+
+ result = dns_message_buildopt(message, opt, 0, udpsize, flags, ednsopts,
+ count);
+ return (result);
+}
+
+static void
+compute_cookie(ns_client_t *client, uint32_t when, uint32_t nonce,
+ const unsigned char *secret, isc_buffer_t *buf) {
+ unsigned char digest[ISC_MAX_MD_SIZE] ISC_NONSTRING = { 0 };
+ STATIC_ASSERT(ISC_MAX_MD_SIZE >= ISC_SIPHASH24_TAG_LENGTH, "You need "
+ "to "
+ "increase "
+ "the digest "
+ "buffer.");
+ STATIC_ASSERT(ISC_MAX_MD_SIZE >= ISC_AES_BLOCK_LENGTH, "You need to "
+ "increase the "
+ "digest "
+ "buffer.");
+
+ switch (client->sctx->cookiealg) {
+ case ns_cookiealg_siphash24: {
+ unsigned char input[16 + 16] ISC_NONSTRING = { 0 };
+ size_t inputlen = 0;
+ isc_netaddr_t netaddr;
+ unsigned char *cp;
+
+ cp = isc_buffer_used(buf);
+ isc_buffer_putmem(buf, client->cookie, 8);
+ isc_buffer_putuint8(buf, NS_COOKIE_VERSION_1);
+ isc_buffer_putuint24(buf, 0); /* Reserved */
+ isc_buffer_putuint32(buf, when);
+
+ memmove(input, cp, 16);
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ switch (netaddr.family) {
+ case AF_INET:
+ cp = (unsigned char *)&netaddr.type.in;
+ memmove(input + 16, cp, 4);
+ inputlen = 20;
+ break;
+ case AF_INET6:
+ cp = (unsigned char *)&netaddr.type.in6;
+ memmove(input + 16, cp, 16);
+ inputlen = 32;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ isc_siphash24(secret, input, inputlen, digest);
+ isc_buffer_putmem(buf, digest, 8);
+ break;
+ }
+ case ns_cookiealg_aes: {
+ unsigned char input[4 + 4 + 16] ISC_NONSTRING = { 0 };
+ isc_netaddr_t netaddr;
+ unsigned char *cp;
+ unsigned int i;
+
+ cp = isc_buffer_used(buf);
+ isc_buffer_putmem(buf, client->cookie, 8);
+ isc_buffer_putuint32(buf, nonce);
+ isc_buffer_putuint32(buf, when);
+ memmove(input, cp, 16);
+ isc_aes128_crypt(secret, input, digest);
+ for (i = 0; i < 8; i++) {
+ input[i] = digest[i] ^ digest[i + 8];
+ }
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ switch (netaddr.family) {
+ case AF_INET:
+ cp = (unsigned char *)&netaddr.type.in;
+ memmove(input + 8, cp, 4);
+ memset(input + 12, 0, 4);
+ isc_aes128_crypt(secret, input, digest);
+ break;
+ case AF_INET6:
+ cp = (unsigned char *)&netaddr.type.in6;
+ memmove(input + 8, cp, 16);
+ isc_aes128_crypt(secret, input, digest);
+ for (i = 0; i < 8; i++) {
+ input[i + 8] = digest[i] ^ digest[i + 8];
+ }
+ isc_aes128_crypt(client->sctx->secret, input + 8,
+ digest);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ for (i = 0; i < 8; i++) {
+ digest[i] ^= digest[i + 8];
+ }
+ isc_buffer_putmem(buf, digest, 8);
+ break;
+ }
+
+ default:
+ UNREACHABLE();
+ }
+}
+
+static void
+process_cookie(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
+ ns_altsecret_t *altsecret;
+ unsigned char dbuf[COOKIE_SIZE];
+ unsigned char *old;
+ isc_stdtime_t now;
+ uint32_t when;
+ uint32_t nonce;
+ isc_buffer_t db;
+
+ /*
+ * If we have already seen a cookie option skip this cookie option.
+ */
+ if ((!client->sctx->answercookie) ||
+ (client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0)
+ {
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return;
+ }
+
+ client->attributes |= NS_CLIENTATTR_WANTCOOKIE;
+
+ ns_stats_increment(client->sctx->nsstats, ns_statscounter_cookiein);
+
+ if (optlen != COOKIE_SIZE) {
+ /*
+ * Not our token.
+ */
+ INSIST(optlen >= 8U);
+ memmove(client->cookie, isc_buffer_current(buf), 8);
+ isc_buffer_forward(buf, (unsigned int)optlen);
+
+ if (optlen == 8U) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookienew);
+ } else {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookiebadsize);
+ }
+ return;
+ }
+
+ /*
+ * Process all of the incoming buffer.
+ */
+ old = isc_buffer_current(buf);
+ memmove(client->cookie, old, 8);
+ isc_buffer_forward(buf, 8);
+ nonce = isc_buffer_getuint32(buf);
+ when = isc_buffer_getuint32(buf);
+ isc_buffer_forward(buf, 8);
+
+ /*
+ * Allow for a 5 minute clock skew between servers sharing a secret.
+ * Only accept COOKIE if we have talked to the client in the last hour.
+ */
+ isc_stdtime_get(&now);
+ if (isc_serial_gt(when, (now + 300)) || /* In the future. */
+ isc_serial_lt(when, (now - 3600)))
+ { /* In the past. */
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookiebadtime);
+ return;
+ }
+
+ isc_buffer_init(&db, dbuf, sizeof(dbuf));
+ compute_cookie(client, when, nonce, client->sctx->secret, &db);
+
+ if (isc_safe_memequal(old, dbuf, COOKIE_SIZE)) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookiematch);
+ client->attributes |= NS_CLIENTATTR_HAVECOOKIE;
+ return;
+ }
+
+ for (altsecret = ISC_LIST_HEAD(client->sctx->altsecrets);
+ altsecret != NULL; altsecret = ISC_LIST_NEXT(altsecret, link))
+ {
+ isc_buffer_init(&db, dbuf, sizeof(dbuf));
+ compute_cookie(client, when, nonce, altsecret->secret, &db);
+ if (isc_safe_memequal(old, dbuf, COOKIE_SIZE)) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookiematch);
+ client->attributes |= NS_CLIENTATTR_HAVECOOKIE;
+ return;
+ }
+ }
+
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_cookienomatch);
+}
+
+static isc_result_t
+process_ecs(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
+ uint16_t family;
+ uint8_t addrlen, addrbytes, scope, *paddr;
+ isc_netaddr_t caddr;
+
+ /*
+ * If we have already seen a ECS option skip this ECS option.
+ */
+ if ((client->attributes & NS_CLIENTATTR_HAVEECS) != 0) {
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * XXXMUKS: Is there any need to repeat these checks here
+ * (except query's scope length) when they are done in the OPT
+ * RDATA fromwire code?
+ */
+
+ if (optlen < 4U) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option too short");
+ return (DNS_R_FORMERR);
+ }
+
+ family = isc_buffer_getuint16(buf);
+ addrlen = isc_buffer_getuint8(buf);
+ scope = isc_buffer_getuint8(buf);
+ optlen -= 4;
+
+ if (scope != 0U) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: invalid scope");
+ return (DNS_R_OPTERR);
+ }
+
+ memset(&caddr, 0, sizeof(caddr));
+ switch (family) {
+ case 0:
+ /*
+ * XXXMUKS: In queries, if FAMILY is set to 0, SOURCE
+ * 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) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: invalid "
+ "address length (%u) for FAMILY=0",
+ addrlen);
+ return (DNS_R_OPTERR);
+ }
+ caddr.family = AF_UNSPEC;
+ break;
+ case 1:
+ if (addrlen > 32U) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: invalid "
+ "address length (%u) for IPv4",
+ addrlen);
+ return (DNS_R_OPTERR);
+ }
+ caddr.family = AF_INET;
+ break;
+ case 2:
+ if (addrlen > 128U) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: invalid "
+ "address length (%u) for IPv6",
+ addrlen);
+ return (DNS_R_OPTERR);
+ }
+ caddr.family = AF_INET6;
+ break;
+ default:
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: invalid family");
+ return (DNS_R_OPTERR);
+ }
+
+ addrbytes = (addrlen + 7) / 8;
+ if (isc_buffer_remaininglength(buf) < addrbytes) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(2),
+ "EDNS client-subnet option: address too short");
+ return (DNS_R_OPTERR);
+ }
+
+ paddr = (uint8_t *)&caddr.type;
+ if (addrbytes != 0U) {
+ memmove(paddr, isc_buffer_current(buf), addrbytes);
+ isc_buffer_forward(buf, addrbytes);
+ optlen -= addrbytes;
+
+ if ((addrlen % 8) != 0) {
+ uint8_t bits = ~0U << (8 - (addrlen % 8));
+ bits &= paddr[addrbytes - 1];
+ if (bits != paddr[addrbytes - 1]) {
+ return (DNS_R_OPTERR);
+ }
+ }
+ }
+
+ memmove(&client->ecs.addr, &caddr, sizeof(caddr));
+ client->ecs.source = addrlen;
+ client->ecs.scope = 0;
+ client->attributes |= NS_CLIENTATTR_HAVEECS;
+
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+process_keytag(ns_client_t *client, isc_buffer_t *buf, size_t optlen) {
+ if (optlen == 0 || (optlen % 2) != 0) {
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return (DNS_R_OPTERR);
+ }
+
+ /* Silently drop additional keytag options. */
+ if (client->keytag != NULL) {
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return (ISC_R_SUCCESS);
+ }
+
+ client->keytag = isc_mem_get(client->mctx, optlen);
+ {
+ client->keytag_len = (uint16_t)optlen;
+ memmove(client->keytag, isc_buffer_current(buf), optlen);
+ }
+ isc_buffer_forward(buf, (unsigned int)optlen);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+process_opt(ns_client_t *client, dns_rdataset_t *opt) {
+ dns_rdata_t rdata;
+ isc_buffer_t optbuf;
+ isc_result_t result;
+ uint16_t optcode;
+ uint16_t optlen;
+
+ /*
+ * Set the client's UDP buffer size.
+ */
+ client->udpsize = opt->rdclass;
+
+ /*
+ * If the requested UDP buffer size is less than 512,
+ * ignore it and use 512.
+ */
+ if (client->udpsize < 512) {
+ client->udpsize = 512;
+ }
+
+ /*
+ * Get the flags out of the OPT record.
+ */
+ client->extflags = (uint16_t)(opt->ttl & 0xFFFF);
+
+ /*
+ * Do we understand this version of EDNS?
+ *
+ * XXXRTH need library support for this!
+ */
+ client->ednsversion = (opt->ttl & 0x00FF0000) >> 16;
+ if (client->ednsversion > DNS_EDNS_VERSION) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_badednsver);
+ result = ns_client_addopt(client, client->message,
+ &client->opt);
+ if (result == ISC_R_SUCCESS) {
+ result = DNS_R_BADVERS;
+ }
+ ns_client_error(client, result);
+ return (result);
+ }
+
+ /* Check for NSID request */
+ result = dns_rdataset_first(opt);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(opt, &rdata);
+ isc_buffer_init(&optbuf, rdata.data, rdata.length);
+ isc_buffer_add(&optbuf, rdata.length);
+ while (isc_buffer_remaininglength(&optbuf) >= 4) {
+ optcode = isc_buffer_getuint16(&optbuf);
+ optlen = isc_buffer_getuint16(&optbuf);
+ switch (optcode) {
+ case DNS_OPT_NSID:
+ if (!WANTNSID(client)) {
+ ns_stats_increment(
+ client->sctx->nsstats,
+ ns_statscounter_nsidopt);
+ }
+ client->attributes |= NS_CLIENTATTR_WANTNSID;
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ case DNS_OPT_COOKIE:
+ process_cookie(client, &optbuf, optlen);
+ break;
+ case DNS_OPT_EXPIRE:
+ if (!WANTEXPIRE(client)) {
+ ns_stats_increment(
+ client->sctx->nsstats,
+ ns_statscounter_expireopt);
+ }
+ client->attributes |= NS_CLIENTATTR_WANTEXPIRE;
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ case DNS_OPT_CLIENT_SUBNET:
+ result = process_ecs(client, &optbuf, optlen);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_error(client, result);
+ return (result);
+ }
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_ecsopt);
+ break;
+ case DNS_OPT_TCP_KEEPALIVE:
+ if (!USEKEEPALIVE(client)) {
+ ns_stats_increment(
+ client->sctx->nsstats,
+ ns_statscounter_keepaliveopt);
+ }
+ client->attributes |=
+ NS_CLIENTATTR_USEKEEPALIVE;
+ isc_nmhandle_keepalive(client->handle, true);
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ case DNS_OPT_PAD:
+ client->attributes |= NS_CLIENTATTR_WANTPAD;
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_padopt);
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ case DNS_OPT_KEY_TAG:
+ result = process_keytag(client, &optbuf,
+ optlen);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_error(client, result);
+ return (result);
+ }
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_keytagopt);
+ break;
+ default:
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_otheropt);
+ isc_buffer_forward(&optbuf, optlen);
+ break;
+ }
+ }
+ }
+
+ ns_stats_increment(client->sctx->nsstats, ns_statscounter_edns0in);
+ client->attributes |= NS_CLIENTATTR_WANTOPT;
+
+ return (result);
+}
+
+void
+ns__client_reset_cb(void *client0) {
+ ns_client_t *client = client0;
+
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3), "reset client");
+
+ /*
+ * We never started processing this client, possible if we're
+ * shutting down, just exit.
+ */
+ if (client->state == NS_CLIENTSTATE_READY) {
+ return;
+ }
+
+ ns_client_endrequest(client);
+ if (client->tcpbuf != NULL) {
+ isc_mem_put(client->manager->send_mctx, client->tcpbuf,
+ client->tcpbuf_size);
+ }
+
+ if (client->keytag != NULL) {
+ isc_mem_put(client->mctx, client->keytag, client->keytag_len);
+ client->keytag_len = 0;
+ }
+
+ client->state = NS_CLIENTSTATE_READY;
+ INSIST(client->recursionquota == NULL);
+
+#ifdef WANT_SINGLETRACE
+ isc_log_setforcelog(false);
+#endif /* WANT_SINGLETRACE */
+}
+
+void
+ns__client_put_cb(void *client0) {
+ ns_client_t *client = client0;
+
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3), "freeing client");
+
+ /*
+ * Call this first because it requires a valid client.
+ */
+ ns_query_free(client);
+
+ client->magic = 0;
+ client->shuttingdown = true;
+
+ isc_mem_put(client->manager->send_mctx, client->sendbuf,
+ NS_CLIENT_SEND_BUFFER_SIZE);
+ if (client->opt != NULL) {
+ INSIST(dns_rdataset_isassociated(client->opt));
+ dns_rdataset_disassociate(client->opt);
+ dns_message_puttemprdataset(client->message, &client->opt);
+ }
+ client_extendederror_reset(client);
+
+ dns_message_detach(&client->message);
+
+ if (client->manager != NULL) {
+ ns_clientmgr_detach(&client->manager);
+ }
+
+ /*
+ * Detaching the task must be done after unlinking from
+ * the manager's lists because the manager accesses
+ * client->task.
+ */
+ if (client->task != NULL) {
+ isc_task_detach(&client->task);
+ }
+
+ /*
+ * Destroy the fetchlock mutex that was created in
+ * ns_query_init().
+ */
+ isc_mutex_destroy(&client->query.fetchlock);
+
+ if (client->sctx != NULL) {
+ ns_server_detach(&client->sctx);
+ }
+
+ isc_mem_detach(&client->mctx);
+}
+
+/*
+ * Handle an incoming request event from the socket (UDP case)
+ * or tcpmsg (TCP case).
+ */
+void
+ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *arg) {
+ ns_client_t *client = NULL;
+ isc_result_t result;
+ isc_result_t sigresult = ISC_R_SUCCESS;
+ isc_buffer_t *buffer = NULL;
+ isc_buffer_t tbuffer;
+ dns_rdataset_t *opt = NULL;
+ const dns_name_t *signame = NULL;
+ bool ra; /* Recursion available. */
+ isc_netaddr_t netaddr;
+ int match;
+ dns_messageid_t id;
+ unsigned int flags;
+ bool notimp;
+ size_t reqsize;
+ dns_aclenv_t *env = NULL;
+#ifdef HAVE_DNSTAP
+ dns_dtmsgtype_t dtmsgtype;
+#endif /* ifdef HAVE_DNSTAP */
+ static const char *ra_reasons[] = {
+ "ACLs not processed yet",
+ "no resolver in view",
+ "recursion not enabled for view",
+ "allow-recursion did not match",
+ "allow-query-cache did not match",
+ "allow-recursion-on did not match",
+ "allow-query-cache-on did not match",
+ };
+ enum refusal_reasons {
+ INVALID,
+ NO_RESOLVER,
+ RECURSION_DISABLED,
+ ALLOW_RECURSION,
+ ALLOW_QUERY_CACHE,
+ ALLOW_RECURSION_ON,
+ ALLOW_QUERY_CACHE_ON
+ } ra_refusal_reason = INVALID;
+
+ if (eresult != ISC_R_SUCCESS) {
+ return;
+ }
+
+ client = isc_nmhandle_getdata(handle);
+ if (client == NULL) {
+ ns_interface_t *ifp = (ns_interface_t *)arg;
+ ns_clientmgr_t *clientmgr =
+ ns_interfacemgr_getclientmgr(ifp->mgr);
+
+ INSIST(VALID_MANAGER(clientmgr));
+
+ client = isc_nmhandle_getextra(handle);
+
+ result = ns__client_setup(client, clientmgr, true);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "allocate new client");
+ } else {
+ result = ns__client_setup(client, NULL, false);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+ }
+
+ client->state = NS_CLIENTSTATE_READY;
+
+ if (client->handle == NULL) {
+ isc_nmhandle_setdata(handle, client, ns__client_reset_cb,
+ ns__client_put_cb);
+ client->handle = handle;
+ }
+
+ if (isc_nmhandle_is_stream(handle)) {
+ client->attributes |= NS_CLIENTATTR_TCP;
+ }
+
+ INSIST(client->recursionquota == NULL);
+
+ INSIST(client->state == NS_CLIENTSTATE_READY);
+
+ (void)atomic_fetch_add_relaxed(&ns_client_requests, 1);
+
+ isc_buffer_init(&tbuffer, region->base, region->length);
+ isc_buffer_add(&tbuffer, region->length);
+ buffer = &tbuffer;
+
+ client->peeraddr = isc_nmhandle_peeraddr(handle);
+ client->peeraddr_valid = true;
+
+ reqsize = isc_buffer_usedlength(buffer);
+
+ client->state = NS_CLIENTSTATE_WORKING;
+
+ TIME_NOW(&client->requesttime);
+ client->tnow = client->requesttime;
+ client->now = isc_time_seconds(&client->tnow);
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+
+#if NS_CLIENT_DROPPORT
+ if (ns_client_dropport(isc_sockaddr_getport(&client->peeraddr)) ==
+ DROPPORT_REQUEST)
+ {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(10),
+ "dropped request: suspicious port");
+ isc_nm_bad_request(handle);
+ return;
+ }
+#endif /* if NS_CLIENT_DROPPORT */
+
+ env = client->manager->aclenv;
+ if (client->sctx->blackholeacl != NULL &&
+ (dns_acl_match(&netaddr, NULL, client->sctx->blackholeacl, env,
+ &match, NULL) == ISC_R_SUCCESS) &&
+ match > 0)
+ {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(10),
+ "dropped request: blackholed peer");
+ isc_nm_bad_request(handle);
+ return;
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3), "%s request",
+ TCP_CLIENT(client) ? "TCP" : "UDP");
+
+ result = dns_message_peekheader(buffer, &id, &flags);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * There isn't enough header to determine whether
+ * this was a request or a response. Drop it.
+ */
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(10),
+ "dropped request: invalid message header");
+ isc_nm_bad_request(handle);
+ return;
+ }
+
+#ifdef WANT_SINGLETRACE
+ if (id == 0) {
+ isc_log_setforcelog(true);
+ }
+#endif /* WANT_SINGLETRACE */
+
+ /*
+ * The client object handles requests, not responses.
+ * If this is a UDP response, forward it to the dispatcher.
+ * If it's a TCP response, discard it here.
+ */
+ if ((flags & DNS_MESSAGEFLAG_QR) != 0) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(10),
+ "dropped request: unexpected response");
+ isc_nm_bad_request(handle);
+ return;
+ }
+
+ /*
+ * Update some statistics counters. Don't count responses.
+ */
+ if (isc_sockaddr_pf(&client->peeraddr) == PF_INET) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_requestv4);
+ } else {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_requestv6);
+ }
+ if (TCP_CLIENT(client)) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_requesttcp);
+ switch (isc_sockaddr_pf(&client->peeraddr)) {
+ case AF_INET:
+ isc_stats_increment(client->sctx->tcpinstats4,
+ ISC_MIN((int)reqsize / 16, 18));
+ break;
+ case AF_INET6:
+ isc_stats_increment(client->sctx->tcpinstats6,
+ ISC_MIN((int)reqsize / 16, 18));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ } else {
+ switch (isc_sockaddr_pf(&client->peeraddr)) {
+ case AF_INET:
+ isc_stats_increment(client->sctx->udpinstats4,
+ ISC_MIN((int)reqsize / 16, 18));
+ break;
+ case AF_INET6:
+ isc_stats_increment(client->sctx->udpinstats6,
+ ISC_MIN((int)reqsize / 16, 18));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ /*
+ * It's a request. Parse it.
+ */
+ result = dns_message_parse(client->message, buffer, 0);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Parsing the request failed. Send a response
+ * (typically FORMERR or SERVFAIL).
+ */
+ if (result == DNS_R_OPTERR) {
+ (void)ns_client_addopt(client, client->message,
+ &client->opt);
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "message parsing failed: %s",
+ isc_result_totext(result));
+ if (result == ISC_R_NOSPACE || result == DNS_R_BADTSIG) {
+ result = DNS_R_FORMERR;
+ }
+ ns_client_error(client, result);
+ return;
+ }
+
+ /*
+ * Disable pipelined TCP query processing if necessary.
+ */
+ if (TCP_CLIENT(client) &&
+ (client->message->opcode != dns_opcode_query ||
+ (client->sctx->keepresporder != NULL &&
+ dns_acl_allowed(&netaddr, NULL, client->sctx->keepresporder,
+ env))))
+ {
+ isc_nm_sequential(handle);
+ }
+
+ dns_opcodestats_increment(client->sctx->opcodestats,
+ client->message->opcode);
+ switch (client->message->opcode) {
+ case dns_opcode_query:
+ case dns_opcode_update:
+ case dns_opcode_notify:
+ notimp = false;
+ break;
+ case dns_opcode_iquery:
+ default:
+ notimp = true;
+ break;
+ }
+
+ client->message->rcode = dns_rcode_noerror;
+ client->ede = NULL;
+
+ /*
+ * Deal with EDNS.
+ */
+ if ((client->sctx->options & NS_SERVER_NOEDNS) != 0) {
+ opt = NULL;
+ } else {
+ opt = dns_message_getopt(client->message);
+ }
+
+ client->ecs.source = 0;
+ client->ecs.scope = 0;
+
+ if (opt != NULL) {
+ /*
+ * Are returning FORMERR to all EDNS queries?
+ * Simulate a STD13 compliant server.
+ */
+ if ((client->sctx->options & NS_SERVER_EDNSFORMERR) != 0) {
+ ns_client_error(client, DNS_R_FORMERR);
+ return;
+ }
+
+ /*
+ * Are returning NOTIMP to all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_EDNSNOTIMP) != 0) {
+ ns_client_error(client, DNS_R_NOTIMP);
+ return;
+ }
+
+ /*
+ * Are returning REFUSED to all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_EDNSREFUSED) != 0) {
+ ns_client_error(client, DNS_R_REFUSED);
+ return;
+ }
+
+ /*
+ * Are we dropping all EDNS queries?
+ */
+ if ((client->sctx->options & NS_SERVER_DROPEDNS) != 0) {
+ ns_client_drop(client, ISC_R_SUCCESS);
+ return;
+ }
+
+ result = process_opt(client, opt);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+ }
+
+ if (client->message->rdclass == 0) {
+ if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0 &&
+ client->message->opcode == dns_opcode_query &&
+ client->message->counts[DNS_SECTION_QUESTION] == 0U)
+ {
+ result = dns_message_reply(client->message, true);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_error(client, result);
+ return;
+ }
+
+ if (notimp) {
+ client->message->rcode = dns_rcode_notimp;
+ }
+
+ ns_client_send(client);
+ return;
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "message class could not be determined");
+ ns_client_dumpmessage(client, "message class could not be "
+ "determined");
+ ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
+ return;
+ }
+
+ client->destsockaddr = isc_nmhandle_localaddr(handle);
+ isc_netaddr_fromsockaddr(&client->destaddr, &client->destsockaddr);
+
+ result = client->sctx->matchingview(&netaddr, &client->destaddr,
+ client->message, env, &sigresult,
+ &client->view);
+ if (result != ISC_R_SUCCESS) {
+ char classname[DNS_RDATACLASS_FORMATSIZE];
+
+ /*
+ * Do a dummy TSIG verification attempt so that the
+ * response will have a TSIG if the query did, as
+ * required by RFC2845.
+ */
+ isc_buffer_t b;
+ isc_region_t *r;
+
+ dns_message_resetsig(client->message);
+
+ r = dns_message_getrawmessage(client->message);
+ isc_buffer_init(&b, r->base, r->length);
+ isc_buffer_add(&b, r->length);
+ (void)dns_tsig_verify(&b, client->message, NULL, NULL);
+
+ dns_rdataclass_format(client->message->rdclass, classname,
+ sizeof(classname));
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "no matching view in class '%s'", classname);
+ ns_client_dumpmessage(client, "no matching view in class");
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
+ ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED);
+ return;
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(5), "using view '%s'", client->view->name);
+
+ /*
+ * Check for a signature. We log bad signatures regardless of
+ * whether they ultimately cause the request to be rejected or
+ * not. We do not log the lack of a signature unless we are
+ * debugging.
+ */
+ client->signer = NULL;
+ dns_name_init(&client->signername, NULL);
+ result = dns_message_signer(client->message, &client->signername);
+ if (result != ISC_R_NOTFOUND) {
+ signame = NULL;
+ if (dns_message_gettsig(client->message, &signame) != NULL) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_tsigin);
+ } else {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_sig0in);
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(&client->signername, namebuf, sizeof(namebuf));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "request has valid signature: %s", namebuf);
+ client->signer = &client->signername;
+ } else if (result == ISC_R_NOTFOUND) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "request is not signed");
+ } else if (result == DNS_R_NOIDENTITY) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "request is signed by a nonauthoritative key");
+ } else {
+ char tsigrcode[64];
+ isc_buffer_t b;
+ dns_rcode_t status;
+ isc_result_t tresult;
+
+ /* There is a signature, but it is bad. */
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_invalidsig);
+ signame = NULL;
+ if (dns_message_gettsig(client->message, &signame) != NULL) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char cnamebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(signame, namebuf, sizeof(namebuf));
+ status = client->message->tsigstatus;
+ isc_buffer_init(&b, tsigrcode, sizeof(tsigrcode) - 1);
+ tresult = dns_tsigrcode_totext(status, &b);
+ INSIST(tresult == ISC_R_SUCCESS);
+ tsigrcode[isc_buffer_usedlength(&b)] = '\0';
+ if (client->message->tsigkey->generated) {
+ dns_name_format(
+ client->message->tsigkey->creator,
+ cnamebuf, sizeof(cnamebuf));
+ ns_client_log(
+ client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_ERROR,
+ "request has invalid signature: "
+ "TSIG %s (%s): %s (%s)",
+ namebuf, cnamebuf,
+ isc_result_totext(result), tsigrcode);
+ } else {
+ ns_client_log(
+ client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_ERROR,
+ "request has invalid signature: "
+ "TSIG %s: %s (%s)",
+ namebuf, isc_result_totext(result),
+ tsigrcode);
+ }
+ } else {
+ status = client->message->sig0status;
+ isc_buffer_init(&b, tsigrcode, sizeof(tsigrcode) - 1);
+ tresult = dns_tsigrcode_totext(status, &b);
+ INSIST(tresult == ISC_R_SUCCESS);
+ tsigrcode[isc_buffer_usedlength(&b)] = '\0';
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_ERROR,
+ "request has invalid signature: %s (%s)",
+ isc_result_totext(result), tsigrcode);
+ }
+
+ /*
+ * Accept update messages signed by unknown keys so that
+ * update forwarding works transparently through slaves
+ * that don't have all the same keys as the primary.
+ */
+ if (!(client->message->tsigstatus == dns_tsigerror_badkey &&
+ client->message->opcode == dns_opcode_update))
+ {
+ ns_client_error(client, sigresult);
+ return;
+ }
+ }
+
+ /*
+ * Decide whether recursive service is available to this client.
+ * We do this here rather than in the query code so that we can
+ * set the RA bit correctly on all kinds of responses, not just
+ * responses to ordinary queries. Note if you can't query the
+ * cache there is no point in setting RA.
+ */
+ ra = false;
+
+ /* must be initialized before ns_client_log uses it as index */
+ if (client->view->resolver == NULL) {
+ ra_refusal_reason = NO_RESOLVER;
+ } else if (!client->view->recursion) {
+ ra_refusal_reason = RECURSION_DISABLED;
+ } else if (ns_client_checkaclsilent(client, NULL,
+ client->view->recursionacl,
+ true) != ISC_R_SUCCESS)
+ {
+ ra_refusal_reason = ALLOW_RECURSION;
+ } else if (ns_client_checkaclsilent(client, NULL,
+ client->view->cacheacl,
+ true) != ISC_R_SUCCESS)
+ {
+ ra_refusal_reason = ALLOW_QUERY_CACHE;
+ } else if (ns_client_checkaclsilent(client, &client->destaddr,
+ client->view->recursiononacl,
+ true) != ISC_R_SUCCESS)
+ {
+ ra_refusal_reason = ALLOW_RECURSION_ON;
+ } else if (ns_client_checkaclsilent(client, &client->destaddr,
+ client->view->cacheonacl,
+ true) != ISC_R_SUCCESS)
+ {
+ ra_refusal_reason = ALLOW_QUERY_CACHE_ON;
+ } else {
+ ra = true;
+ client->attributes |= NS_CLIENTATTR_RA;
+ }
+
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3),
+ ra ? "recursion available"
+ : "recursion not available (%s)",
+ ra_reasons[ra_refusal_reason]);
+
+ /*
+ * Adjust maximum UDP response size for this client.
+ */
+ if (client->udpsize > 512) {
+ dns_peer_t *peer = NULL;
+ uint16_t udpsize = client->view->maxudp;
+ (void)dns_peerlist_peerbyaddr(client->view->peers, &netaddr,
+ &peer);
+ if (peer != NULL) {
+ dns_peer_getmaxudp(peer, &udpsize);
+ }
+ if (client->udpsize > udpsize) {
+ client->udpsize = udpsize;
+ }
+ }
+
+ /*
+ * Dispatch the request.
+ */
+ switch (client->message->opcode) {
+ case dns_opcode_query:
+ CTRACE("query");
+#ifdef HAVE_DNSTAP
+ if (ra && (client->message->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ dtmsgtype = DNS_DTTYPE_CQ;
+ } else {
+ dtmsgtype = DNS_DTTYPE_AQ;
+ }
+
+ dns_dt_send(client->view, dtmsgtype, &client->peeraddr,
+ &client->destsockaddr, TCP_CLIENT(client), NULL,
+ &client->requesttime, NULL, buffer);
+#endif /* HAVE_DNSTAP */
+
+ ns_query_start(client, handle);
+ break;
+ case dns_opcode_update:
+ CTRACE("update");
+#ifdef HAVE_DNSTAP
+ dns_dt_send(client->view, DNS_DTTYPE_UQ, &client->peeraddr,
+ &client->destsockaddr, TCP_CLIENT(client), NULL,
+ &client->requesttime, NULL, buffer);
+#endif /* HAVE_DNSTAP */
+ ns_client_settimeout(client, 60);
+ ns_update_start(client, handle, sigresult);
+ break;
+ case dns_opcode_notify:
+ CTRACE("notify");
+ ns_client_settimeout(client, 60);
+ ns_notify_start(client, handle);
+ break;
+ case dns_opcode_iquery:
+ CTRACE("iquery");
+ ns_client_error(client, DNS_R_NOTIMP);
+ break;
+ default:
+ CTRACE("unknown opcode");
+ ns_client_error(client, DNS_R_NOTIMP);
+ }
+}
+
+isc_result_t
+ns__client_tcpconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+ ns_interface_t *ifp = (ns_interface_t *)arg;
+ dns_aclenv_t *env = ns_interfacemgr_getaclenv(ifp->mgr);
+ ns_server_t *sctx = ns_interfacemgr_getserver(ifp->mgr);
+ unsigned int tcpquota;
+ isc_sockaddr_t peeraddr;
+ isc_netaddr_t netaddr;
+ int match;
+
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (handle != NULL) {
+ peeraddr = isc_nmhandle_peeraddr(handle);
+ isc_netaddr_fromsockaddr(&netaddr, &peeraddr);
+
+ if (sctx->blackholeacl != NULL &&
+ (dns_acl_match(&netaddr, NULL, sctx->blackholeacl, env,
+ &match, NULL) == ISC_R_SUCCESS) &&
+ match > 0)
+ {
+ return (ISC_R_CONNREFUSED);
+ }
+ }
+
+ tcpquota = isc_quota_getused(&sctx->tcpquota);
+ ns_stats_update_if_greater(sctx->nsstats, ns_statscounter_tcphighwater,
+ tcpquota);
+
+ return (ISC_R_SUCCESS);
+}
+
+isc_result_t
+ns__client_setup(ns_client_t *client, ns_clientmgr_t *mgr, bool new) {
+ isc_result_t result;
+
+ /*
+ * Caller must be holding the manager lock.
+ *
+ * Note: creating a client does not add the client to the
+ * manager's client list or set the client's manager pointer.
+ * The caller is responsible for that.
+ */
+
+ if (new) {
+ REQUIRE(VALID_MANAGER(mgr));
+ REQUIRE(client != NULL);
+ REQUIRE(mgr->tid == isc_nm_tid());
+
+ *client = (ns_client_t){ .magic = 0, .tid = mgr->tid };
+
+ isc_mem_attach(mgr->mctx, &client->mctx);
+ clientmgr_attach(mgr, &client->manager);
+ ns_server_attach(mgr->sctx, &client->sctx);
+ isc_task_attach(mgr->task, &client->task);
+
+ dns_message_create(client->mctx, DNS_MESSAGE_INTENTPARSE,
+ &client->message);
+
+ client->sendbuf = isc_mem_get(client->manager->send_mctx,
+ NS_CLIENT_SEND_BUFFER_SIZE);
+ /*
+ * Set magic earlier than usual because ns_query_init()
+ * and the functions it calls will require it.
+ */
+ client->magic = NS_CLIENT_MAGIC;
+ result = ns_query_init(client);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ } else {
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(client->tid == isc_nm_tid());
+
+ ns_clientmgr_t *oldmgr = client->manager;
+ ns_server_t *sctx = client->sctx;
+ isc_task_t *task = client->task;
+ unsigned char *sendbuf = client->sendbuf;
+ dns_message_t *message = client->message;
+ isc_mem_t *oldmctx = client->mctx;
+ ns_query_t query = client->query;
+ int tid = client->tid;
+
+ /*
+ * Retain these values from the existing client, but
+ * zero every thing else.
+ */
+ *client = (ns_client_t){ .magic = 0,
+ .mctx = oldmctx,
+ .manager = oldmgr,
+ .sctx = sctx,
+ .task = task,
+ .sendbuf = sendbuf,
+ .message = message,
+ .query = query,
+ .tid = tid };
+ }
+
+ client->query.attributes &= ~NS_QUERYATTR_ANSWERED;
+ client->state = NS_CLIENTSTATE_INACTIVE;
+ client->udpsize = 512;
+ client->ednsversion = -1;
+ dns_name_init(&client->signername, NULL);
+ dns_ecs_init(&client->ecs);
+ isc_sockaddr_any(&client->formerrcache.addr);
+ client->formerrcache.time = 0;
+ client->formerrcache.id = 0;
+ ISC_LINK_INIT(client, rlink);
+ client->rcode_override = -1; /* not set */
+
+ client->magic = NS_CLIENT_MAGIC;
+
+ CTRACE("client_setup");
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ if (client->sendbuf != NULL) {
+ isc_mem_put(client->manager->send_mctx, client->sendbuf,
+ NS_CLIENT_SEND_BUFFER_SIZE);
+ }
+
+ if (client->message != NULL) {
+ dns_message_detach(&client->message);
+ }
+
+ if (client->task != NULL) {
+ isc_task_detach(&client->task);
+ }
+
+ if (client->manager != NULL) {
+ ns_clientmgr_detach(&client->manager);
+ }
+ isc_mem_detach(&client->mctx);
+ if (client->sctx != NULL) {
+ ns_server_detach(&client->sctx);
+ }
+
+ return (result);
+}
+
+bool
+ns_client_shuttingdown(ns_client_t *client) {
+ return (client->shuttingdown);
+}
+
+/***
+ *** Client Manager
+ ***/
+
+static void
+clientmgr_attach(ns_clientmgr_t *source, ns_clientmgr_t **targetp) {
+ int32_t oldrefs;
+
+ REQUIRE(VALID_MANAGER(source));
+ REQUIRE(targetp != NULL && *targetp == NULL);
+
+ oldrefs = isc_refcount_increment0(&source->references);
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3), "clientmgr @%p attach: %d", source,
+ oldrefs + 1);
+
+ *targetp = source;
+}
+
+void
+ns_clientmgr_detach(ns_clientmgr_t **mp) {
+ int32_t oldrefs;
+ ns_clientmgr_t *mgr = *mp;
+ *mp = NULL;
+
+ oldrefs = isc_refcount_decrement(&mgr->references);
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_CLIENT,
+ ISC_LOG_DEBUG(3), "clientmgr @%p detach: %d", mgr,
+ oldrefs - 1);
+ if (oldrefs == 1) {
+ clientmgr_destroy(mgr);
+ }
+}
+
+static void
+clientmgr_destroy(ns_clientmgr_t *manager) {
+ MTRACE("clientmgr_destroy");
+
+ isc_refcount_destroy(&manager->references);
+ manager->magic = 0;
+
+ dns_aclenv_detach(&manager->aclenv);
+
+ isc_mutex_destroy(&manager->reclock);
+
+ isc_task_detach(&manager->task);
+ ns_server_detach(&manager->sctx);
+
+ isc_mem_detach(&manager->send_mctx);
+
+ isc_mem_putanddetach(&manager->mctx, manager, sizeof(*manager));
+}
+
+isc_result_t
+ns_clientmgr_create(ns_server_t *sctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_aclenv_t *aclenv, int tid,
+ ns_clientmgr_t **managerp) {
+ ns_clientmgr_t *manager = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_result_t result;
+
+ isc_mem_create(&mctx);
+ isc_mem_setname(mctx, "clientmgr");
+
+ manager = isc_mem_get(mctx, sizeof(*manager));
+ *manager = (ns_clientmgr_t){ .magic = 0, .mctx = mctx };
+
+ isc_mutex_init(&manager->reclock);
+
+ manager->taskmgr = taskmgr;
+ manager->timermgr = timermgr;
+ manager->tid = tid;
+
+ dns_aclenv_attach(aclenv, &manager->aclenv);
+
+ result = isc_task_create_bound(manager->taskmgr, 20, &manager->task,
+ manager->tid);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ isc_task_setname(manager->task, "clientmgr", NULL);
+
+ isc_refcount_init(&manager->references, 1);
+ manager->sctx = NULL;
+ ns_server_attach(sctx, &manager->sctx);
+
+ ISC_LIST_INIT(manager->recursing);
+
+ /*
+ * We create specialised per-worker memory context specifically
+ * dedicated and tuned for allocating send buffers as it is a very
+ * common operation. Not doing so may result in excessive memory
+ * use in certain workloads.
+ *
+ * Please see this thread for more details:
+ *
+ * https://github.com/jemalloc/jemalloc/issues/2483
+ *
+ * In particular, this information from the jemalloc developers is
+ * of the most interest:
+ *
+ * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1639019699
+ * https://github.com/jemalloc/jemalloc/issues/2483#issuecomment-1698173849
+ *
+ * In essence, we use the following memory management strategy:
+ *
+ * 1. We use a per-worker memory arena for send buffers memory
+ * allocation to reduce lock contention (In reality, we create a
+ * per-client manager arena, but we have one client manager per
+ * worker).
+ *
+ * 2. The automatically created arenas settings remain unchanged
+ * and may be controlled by users (e.g. by setting the
+ * "MALLOC_CONF" variable).
+ *
+ * 3. We attune the arenas to not use dirty pages cache as the
+ * cache would have a poor reuse rate, and that is known to
+ * significantly contribute to excessive memory use.
+ *
+ * 4. There is no strict need for the dirty cache, as there is a
+ * per arena bin for each allocation size, so because we initially
+ * allocate strictly 64K per send buffer (enough for a DNS
+ * message), allocations would get directed to one bin (an "object
+ * pool" or a "slab") maintained within an arena. That is, there
+ * is an object pool already, specifically to optimise for the
+ * case of frequent allocations of objects of the given size. The
+ * object pool should suffice our needs, as we will end up
+ * recycling the objects from there without the need to back it by
+ * an additional layer of dirty pages cache. The dirty pages cache
+ * would have worked better in the case when there are more
+ * allocation bins involved due to a higher reuse rate (the case
+ * of a more "generic" memory management).
+ */
+ isc_mem_create_arena(&manager->send_mctx);
+ isc_mem_setname(manager->send_mctx, "sendbufs");
+ (void)isc_mem_arena_set_dirty_decay_ms(manager->send_mctx, 0);
+ /*
+ * Disable muzzy pages cache too, as versions < 5.2.0 have it
+ * enabled by default. The muzzy pages cache goes right below the
+ * dirty pages cache and backs it.
+ */
+ (void)isc_mem_arena_set_muzzy_decay_ms(manager->send_mctx, 0);
+
+ manager->magic = MANAGER_MAGIC;
+
+ MTRACE("create");
+
+ *managerp = manager;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_clientmgr_shutdown(ns_clientmgr_t *manager) {
+ ns_client_t *client;
+
+ REQUIRE(VALID_MANAGER(manager));
+
+ MTRACE("destroy");
+
+ LOCK(&manager->reclock);
+ for (client = ISC_LIST_HEAD(manager->recursing); client != NULL;
+ client = ISC_LIST_NEXT(client, rlink))
+ {
+ ns_query_cancel(client);
+ }
+ UNLOCK(&manager->reclock);
+}
+
+isc_sockaddr_t *
+ns_client_getsockaddr(ns_client_t *client) {
+ return (&client->peeraddr);
+}
+
+isc_sockaddr_t *
+ns_client_getdestaddr(ns_client_t *client) {
+ return (&client->destsockaddr);
+}
+
+isc_result_t
+ns_client_checkaclsilent(ns_client_t *client, isc_netaddr_t *netaddr,
+ dns_acl_t *acl, bool default_allow) {
+ isc_result_t result;
+ dns_aclenv_t *env = client->manager->aclenv;
+ isc_netaddr_t tmpnetaddr;
+ int match;
+ isc_sockaddr_t local;
+
+ if (acl == NULL) {
+ if (default_allow) {
+ goto allow;
+ } else {
+ goto deny;
+ }
+ }
+
+ if (netaddr == NULL) {
+ isc_netaddr_fromsockaddr(&tmpnetaddr, &client->peeraddr);
+ netaddr = &tmpnetaddr;
+ }
+
+ local = isc_nmhandle_localaddr(client->handle);
+ result = dns_acl_match_port_transport(
+ netaddr, isc_sockaddr_getport(&local),
+ isc_nm_socket_type(client->handle),
+ isc_nm_has_encryption(client->handle), client->signer, acl, env,
+ &match, NULL);
+
+ if (result != ISC_R_SUCCESS) {
+ goto deny; /* Internal error, already logged. */
+ }
+
+ if (match > 0) {
+ goto allow;
+ }
+ goto deny; /* Negative match or no match. */
+
+allow:
+ return (ISC_R_SUCCESS);
+
+deny:
+ return (DNS_R_REFUSED);
+}
+
+isc_result_t
+ns_client_checkacl(ns_client_t *client, isc_sockaddr_t *sockaddr,
+ const char *opname, dns_acl_t *acl, bool default_allow,
+ int log_level) {
+ isc_result_t result;
+ isc_netaddr_t netaddr;
+
+ if (sockaddr != NULL) {
+ isc_netaddr_fromsockaddr(&netaddr, sockaddr);
+ }
+
+ result = ns_client_checkaclsilent(client, sockaddr ? &netaddr : NULL,
+ acl, default_allow);
+
+ if (result == ISC_R_SUCCESS) {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(3),
+ "%s approved", opname);
+ } else {
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_CLIENT, log_level, "%s denied",
+ opname);
+ }
+ return (result);
+}
+
+static void
+ns_client_name(ns_client_t *client, char *peerbuf, size_t len) {
+ if (client->peeraddr_valid) {
+ isc_sockaddr_format(&client->peeraddr, peerbuf,
+ (unsigned int)len);
+ } else {
+ snprintf(peerbuf, len, "@%p", client);
+ }
+}
+
+void
+ns_client_logv(ns_client_t *client, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt,
+ va_list ap) {
+ char msgbuf[4096];
+ char signerbuf[DNS_NAME_FORMATSIZE], qnamebuf[DNS_NAME_FORMATSIZE];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ const char *viewname = "";
+ const char *sep1 = "", *sep2 = "", *sep3 = "", *sep4 = "";
+ const char *signer = "", *qname = "";
+ dns_name_t *q = NULL;
+
+ REQUIRE(client != NULL);
+
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+
+ if (client->signer != NULL) {
+ dns_name_format(client->signer, signerbuf, sizeof(signerbuf));
+ sep1 = "/key ";
+ signer = signerbuf;
+ }
+
+ q = client->query.origqname != NULL ? client->query.origqname
+ : client->query.qname;
+ if (q != NULL) {
+ dns_name_format(q, qnamebuf, sizeof(qnamebuf));
+ sep2 = " (";
+ sep3 = ")";
+ qname = qnamebuf;
+ }
+
+ if (client->view != NULL && strcmp(client->view->name, "_bind") != 0 &&
+ strcmp(client->view->name, "_default") != 0)
+ {
+ sep4 = ": view ";
+ viewname = client->view->name;
+ }
+
+ if (client->peeraddr_valid) {
+ isc_sockaddr_format(&client->peeraddr, peerbuf,
+ sizeof(peerbuf));
+ } else {
+ snprintf(peerbuf, sizeof(peerbuf), "(no-peer)");
+ }
+
+ isc_log_write(ns_lctx, category, module, level,
+ "client @%p %s%s%s%s%s%s%s%s: %s", client, peerbuf, sep1,
+ signer, sep2, qname, sep3, sep4, viewname, msgbuf);
+}
+
+void
+ns_client_log(ns_client_t *client, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...) {
+ va_list ap;
+
+ if (!isc_log_wouldlog(ns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ ns_client_logv(client, category, module, level, fmt, ap);
+ va_end(ap);
+}
+
+void
+ns_client_aclmsg(const char *msg, const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataclass_t rdclass, char *buf, size_t len) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+ dns_rdatatype_format(type, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf));
+ (void)snprintf(buf, len, "%s '%s/%s/%s'", msg, namebuf, typebuf,
+ classbuf);
+}
+
+static void
+ns_client_dumpmessage(ns_client_t *client, const char *reason) {
+ isc_buffer_t buffer;
+ char *buf = NULL;
+ int len = 1024;
+ isc_result_t result;
+
+ if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
+ return;
+ }
+
+ /*
+ * Note that these are multiline debug messages. We want a newline
+ * to appear in the log after each message.
+ */
+
+ do {
+ buf = isc_mem_get(client->mctx, len);
+ isc_buffer_init(&buffer, buf, len);
+ result = dns_message_totext(
+ client->message, &dns_master_style_debug, 0, &buffer);
+ if (result == ISC_R_NOSPACE) {
+ isc_mem_put(client->mctx, buf, len);
+ len += 1024;
+ } else if (result == ISC_R_SUCCESS) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "%s\n%.*s", reason,
+ (int)isc_buffer_usedlength(&buffer), buf);
+ }
+ } while (result == ISC_R_NOSPACE);
+
+ if (buf != NULL) {
+ isc_mem_put(client->mctx, buf, len);
+ }
+}
+
+void
+ns_client_dumprecursing(FILE *f, ns_clientmgr_t *manager) {
+ ns_client_t *client;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char original[DNS_NAME_FORMATSIZE];
+ char peerbuf[ISC_SOCKADDR_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ const char *name;
+ const char *sep;
+ const char *origfor;
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(VALID_MANAGER(manager));
+
+ LOCK(&manager->reclock);
+ client = ISC_LIST_HEAD(manager->recursing);
+ while (client != NULL) {
+ INSIST(client->state == NS_CLIENTSTATE_RECURSING);
+
+ ns_client_name(client, peerbuf, sizeof(peerbuf));
+ if (client->view != NULL &&
+ strcmp(client->view->name, "_bind") != 0 &&
+ strcmp(client->view->name, "_default") != 0)
+ {
+ name = client->view->name;
+ sep = ": view ";
+ } else {
+ name = "";
+ sep = "";
+ }
+
+ LOCK(&client->query.fetchlock);
+ INSIST(client->query.qname != NULL);
+ dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
+ if (client->query.qname != client->query.origqname &&
+ client->query.origqname != NULL)
+ {
+ origfor = " for ";
+ dns_name_format(client->query.origqname, original,
+ sizeof(original));
+ } else {
+ origfor = "";
+ original[0] = '\0';
+ }
+ rdataset = ISC_LIST_HEAD(client->query.qname->list);
+ if (rdataset == NULL && client->query.origqname != NULL) {
+ rdataset = ISC_LIST_HEAD(client->query.origqname->list);
+ }
+ if (rdataset != NULL) {
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ } else {
+ strlcpy(typebuf, "-", sizeof(typebuf));
+ strlcpy(classbuf, "-", sizeof(classbuf));
+ }
+ UNLOCK(&client->query.fetchlock);
+ fprintf(f,
+ "; client %s%s%s: id %u '%s/%s/%s'%s%s "
+ "requesttime %u\n",
+ peerbuf, sep, name, client->message->id, namebuf,
+ typebuf, classbuf, origfor, original,
+ isc_time_seconds(&client->requesttime));
+ client = ISC_LIST_NEXT(client, rlink);
+ }
+ UNLOCK(&manager->reclock);
+}
+
+void
+ns_client_qnamereplace(ns_client_t *client, dns_name_t *name) {
+ LOCK(&client->query.fetchlock);
+ if (client->query.restarts > 0) {
+ /*
+ * client->query.qname was dynamically allocated.
+ */
+ dns_message_puttempname(client->message, &client->query.qname);
+ }
+ client->query.qname = name;
+ client->query.attributes &= ~NS_QUERYATTR_REDIRECT;
+ UNLOCK(&client->query.fetchlock);
+}
+
+isc_result_t
+ns_client_sourceip(dns_clientinfo_t *ci, isc_sockaddr_t **addrp) {
+ ns_client_t *client = (ns_client_t *)ci->data;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(addrp != NULL);
+
+ *addrp = &client->peeraddr;
+ return (ISC_R_SUCCESS);
+}
+
+dns_rdataset_t *
+ns_client_newrdataset(ns_client_t *client) {
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ rdataset = NULL;
+ result = dns_message_gettemprdataset(client->message, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (NULL);
+ }
+
+ return (rdataset);
+}
+
+void
+ns_client_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp) {
+ dns_rdataset_t *rdataset;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(rdatasetp != NULL);
+
+ rdataset = *rdatasetp;
+
+ if (rdataset != NULL) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ dns_message_puttemprdataset(client->message, rdatasetp);
+ }
+}
+
+isc_result_t
+ns_client_newnamebuf(ns_client_t *client) {
+ isc_buffer_t *dbuf = NULL;
+
+ CTRACE("ns_client_newnamebuf");
+
+ isc_buffer_allocate(client->mctx, &dbuf, 1024);
+ ISC_LIST_APPEND(client->query.namebufs, dbuf, link);
+
+ CTRACE("ns_client_newnamebuf: done");
+ return (ISC_R_SUCCESS);
+}
+
+dns_name_t *
+ns_client_newname(ns_client_t *client, isc_buffer_t *dbuf, isc_buffer_t *nbuf) {
+ dns_name_t *name = NULL;
+ isc_region_t r;
+ isc_result_t result;
+
+ REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) == 0);
+
+ CTRACE("ns_client_newname");
+
+ result = dns_message_gettempname(client->message, &name);
+ if (result != ISC_R_SUCCESS) {
+ CTRACE("ns_client_newname: "
+ "dns_message_gettempname failed: done");
+ return (NULL);
+ }
+ isc_buffer_availableregion(dbuf, &r);
+ isc_buffer_init(nbuf, r.base, r.length);
+ dns_name_setbuffer(name, NULL);
+ dns_name_setbuffer(name, nbuf);
+ client->query.attributes |= NS_QUERYATTR_NAMEBUFUSED;
+
+ CTRACE("ns_client_newname: done");
+ return (name);
+}
+
+isc_buffer_t *
+ns_client_getnamebuf(ns_client_t *client) {
+ isc_buffer_t *dbuf;
+ isc_region_t r;
+
+ CTRACE("ns_client_getnamebuf");
+
+ /*%
+ * Return a name buffer with space for a maximal name, allocating
+ * a new one if necessary.
+ */
+ if (ISC_LIST_EMPTY(client->query.namebufs)) {
+ ns_client_newnamebuf(client);
+ }
+
+ dbuf = ISC_LIST_TAIL(client->query.namebufs);
+ INSIST(dbuf != NULL);
+ isc_buffer_availableregion(dbuf, &r);
+ if (r.length < DNS_NAME_MAXWIRE) {
+ ns_client_newnamebuf(client);
+ dbuf = ISC_LIST_TAIL(client->query.namebufs);
+ isc_buffer_availableregion(dbuf, &r);
+ INSIST(r.length >= 255);
+ }
+ CTRACE("ns_client_getnamebuf: done");
+ return (dbuf);
+}
+
+void
+ns_client_keepname(ns_client_t *client, dns_name_t *name, isc_buffer_t *dbuf) {
+ isc_region_t r;
+
+ CTRACE("ns_client_keepname");
+
+ /*%
+ * 'name' is using space in 'dbuf', but 'dbuf' has not yet been
+ * adjusted to take account of that. We do the adjustment.
+ */
+ REQUIRE((client->query.attributes & NS_QUERYATTR_NAMEBUFUSED) != 0);
+
+ dns_name_toregion(name, &r);
+ isc_buffer_add(dbuf, r.length);
+ dns_name_setbuffer(name, NULL);
+ client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED;
+}
+
+void
+ns_client_releasename(ns_client_t *client, dns_name_t **namep) {
+ /*%
+ * 'name' is no longer needed. Return it to our pool of temporary
+ * names. If it is using a name buffer, relinquish its exclusive
+ * rights on the buffer.
+ */
+
+ CTRACE("ns_client_releasename");
+ client->query.attributes &= ~NS_QUERYATTR_NAMEBUFUSED;
+ dns_message_puttempname(client->message, namep);
+ CTRACE("ns_client_releasename: done");
+}
+
+isc_result_t
+ns_client_newdbversion(ns_client_t *client, unsigned int n) {
+ unsigned int i;
+ ns_dbversion_t *dbversion = NULL;
+
+ for (i = 0; i < n; i++) {
+ dbversion = isc_mem_get(client->mctx, sizeof(*dbversion));
+ *dbversion = (ns_dbversion_t){ 0 };
+ ISC_LIST_INITANDAPPEND(client->query.freeversions, dbversion,
+ link);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static ns_dbversion_t *
+client_getdbversion(ns_client_t *client) {
+ ns_dbversion_t *dbversion = NULL;
+
+ if (ISC_LIST_EMPTY(client->query.freeversions)) {
+ ns_client_newdbversion(client, 1);
+ }
+ dbversion = ISC_LIST_HEAD(client->query.freeversions);
+ INSIST(dbversion != NULL);
+ ISC_LIST_UNLINK(client->query.freeversions, dbversion, link);
+
+ return (dbversion);
+}
+
+ns_dbversion_t *
+ns_client_findversion(ns_client_t *client, dns_db_t *db) {
+ ns_dbversion_t *dbversion;
+
+ for (dbversion = ISC_LIST_HEAD(client->query.activeversions);
+ dbversion != NULL; dbversion = ISC_LIST_NEXT(dbversion, link))
+ {
+ if (dbversion->db == db) {
+ break;
+ }
+ }
+
+ if (dbversion == NULL) {
+ /*
+ * This is a new zone for this query. Add it to
+ * the active list.
+ */
+ dbversion = client_getdbversion(client);
+ if (dbversion == NULL) {
+ return (NULL);
+ }
+ dns_db_attach(db, &dbversion->db);
+ dns_db_currentversion(db, &dbversion->version);
+ dbversion->acl_checked = false;
+ dbversion->queryok = false;
+ ISC_LIST_APPEND(client->query.activeversions, dbversion, link);
+ }
+
+ return (dbversion);
+}
diff --git a/lib/ns/hooks.c b/lib/ns/hooks.c
new file mode 100644
index 0000000..3f614b6
--- /dev/null
+++ b/lib/ns/hooks.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <stdio.h>
+#include <string.h>
+#include <uv.h>
+
+#include <isc/errno.h>
+#include <isc/list.h>
+#include <isc/log.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/view.h>
+
+#include <ns/hooks.h>
+#include <ns/log.h>
+#include <ns/query.h>
+
+#define CHECK(op) \
+ do { \
+ result = (op); \
+ if (result != ISC_R_SUCCESS) { \
+ goto cleanup; \
+ } \
+ } while (0)
+
+struct ns_plugin {
+ isc_mem_t *mctx;
+ uv_lib_t handle;
+ void *inst;
+ char *modpath;
+ ns_plugin_check_t *check_func;
+ ns_plugin_register_t *register_func;
+ ns_plugin_destroy_t *destroy_func;
+ LINK(ns_plugin_t) link;
+};
+
+static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT];
+ns_hooktable_t *ns__hook_table = &default_hooktable;
+
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) {
+ int result;
+
+ /*
+ * On Unix systems, differentiate between paths and filenames.
+ */
+ if (strchr(src, '/') != NULL) {
+ /*
+ * 'src' is an absolute or relative path. Copy it verbatim.
+ */
+ result = snprintf(dst, dstsize, "%s", src);
+ } else {
+ /*
+ * 'src' is a filename. Prepend default plugin directory path.
+ */
+ result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src);
+ }
+
+ if (result < 0) {
+ return (isc_errno_toresult(errno));
+ } else if ((size_t)result >= dstsize) {
+ return (ISC_R_NOSPACE);
+ } else {
+ return (ISC_R_SUCCESS);
+ }
+}
+
+static isc_result_t
+load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name,
+ void **symbolp) {
+ void *symbol = NULL;
+ 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(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
+ "failed to look up symbol %s in "
+ "plugin '%s': %s",
+ symbol_name, modpath, errmsg);
+ return (ISC_R_FAILURE);
+ }
+
+ *symbolp = symbol;
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+unload_plugin(ns_plugin_t **pluginp);
+
+static isc_result_t
+load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
+ isc_result_t result;
+ ns_plugin_t *plugin = NULL;
+ ns_plugin_version_t *version_func = NULL;
+ int version;
+ int r;
+
+ REQUIRE(pluginp != NULL && *pluginp == NULL);
+
+ plugin = isc_mem_get(mctx, sizeof(*plugin));
+ memset(plugin, 0, sizeof(*plugin));
+ isc_mem_attach(mctx, &plugin->mctx);
+
+ plugin->modpath = isc_mem_strdup(plugin->mctx, modpath);
+
+ ISC_LINK_INIT(plugin, link);
+
+ r = uv_dlopen(modpath, &plugin->handle);
+ if (r != 0) {
+ const char *errmsg = uv_dlerror(&plugin->handle);
+ if (errmsg == NULL) {
+ errmsg = "unknown error";
+ }
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
+ "failed to dlopen() plugin '%s': %s", modpath,
+ errmsg);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(&plugin->handle, modpath, "plugin_version",
+ (void **)&version_func));
+
+ version = version_func();
+ if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) ||
+ version > NS_PLUGIN_VERSION)
+ {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
+ NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
+ "plugin API version mismatch: %d/%d", version,
+ NS_PLUGIN_VERSION);
+ CHECK(ISC_R_FAILURE);
+ }
+
+ CHECK(load_symbol(&plugin->handle, modpath, "plugin_check",
+ (void **)&plugin->check_func));
+ CHECK(load_symbol(&plugin->handle, modpath, "plugin_register",
+ (void **)&plugin->register_func));
+ CHECK(load_symbol(&plugin->handle, modpath, "plugin_destroy",
+ (void **)&plugin->destroy_func));
+
+ *pluginp = plugin;
+
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
+ ISC_LOG_ERROR,
+ "failed to dynamically load plugin '%s': %s", modpath,
+ isc_result_totext(result));
+
+ unload_plugin(&plugin);
+
+ return (result);
+}
+
+static void
+unload_plugin(ns_plugin_t **pluginp) {
+ ns_plugin_t *plugin = NULL;
+
+ REQUIRE(pluginp != NULL && *pluginp != NULL);
+
+ plugin = *pluginp;
+ *pluginp = NULL;
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
+ ISC_LOG_DEBUG(1), "unloading plugin '%s'",
+ plugin->modpath);
+
+ if (plugin->inst != NULL) {
+ plugin->destroy_func(&plugin->inst);
+ }
+
+ uv_dlclose(&plugin->handle);
+ isc_mem_free(plugin->mctx, plugin->modpath);
+ isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
+}
+
+isc_result_t
+ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line,
+ isc_mem_t *mctx, isc_log_t *lctx, void *actx,
+ dns_view_t *view) {
+ isc_result_t result;
+ ns_plugin_t *plugin = NULL;
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(lctx != NULL);
+ REQUIRE(view != NULL);
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
+ ISC_LOG_INFO, "loading plugin '%s'", modpath);
+
+ CHECK(load_plugin(mctx, modpath, &plugin));
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
+ ISC_LOG_INFO, "registering plugin '%s'", modpath);
+
+ CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx,
+ lctx, actx, view->hooktable,
+ &plugin->inst));
+
+ ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link);
+
+cleanup:
+ if (result != ISC_R_SUCCESS && plugin != NULL) {
+ unload_plugin(&plugin);
+ }
+
+ return (result);
+}
+
+isc_result_t
+ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
+ isc_log_t *lctx, void *actx) {
+ isc_result_t result;
+ ns_plugin_t *plugin = NULL;
+
+ CHECK(load_plugin(mctx, modpath, &plugin));
+
+ result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx,
+ lctx, actx);
+
+cleanup:
+ if (plugin != NULL) {
+ unload_plugin(&plugin);
+ }
+
+ return (result);
+}
+
+void
+ns_hooktable_init(ns_hooktable_t *hooktable) {
+ int i;
+
+ for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
+ ISC_LIST_INIT((*hooktable)[i]);
+ }
+}
+
+isc_result_t
+ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
+ ns_hooktable_t *hooktable = NULL;
+
+ REQUIRE(tablep != NULL && *tablep == NULL);
+
+ hooktable = isc_mem_get(mctx, sizeof(*hooktable));
+
+ ns_hooktable_init(hooktable);
+
+ *tablep = hooktable;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_hooktable_free(isc_mem_t *mctx, void **tablep) {
+ ns_hooktable_t *table = NULL;
+ ns_hook_t *hook = NULL, *next = NULL;
+ int i = 0;
+
+ REQUIRE(tablep != NULL && *tablep != NULL);
+
+ table = *tablep;
+ *tablep = NULL;
+
+ for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
+ for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL;
+ hook = next)
+ {
+ next = ISC_LIST_NEXT(hook, link);
+ ISC_LIST_UNLINK((*table)[i], hook, link);
+ if (hook->mctx != NULL) {
+ isc_mem_putanddetach(&hook->mctx, hook,
+ sizeof(*hook));
+ }
+ }
+ }
+
+ isc_mem_put(mctx, table, sizeof(*table));
+}
+
+void
+ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
+ ns_hookpoint_t hookpoint, const ns_hook_t *hook) {
+ ns_hook_t *copy = NULL;
+
+ REQUIRE(hooktable != NULL);
+ REQUIRE(mctx != NULL);
+ REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT);
+ REQUIRE(hook != NULL);
+
+ copy = isc_mem_get(mctx, sizeof(*copy));
+ memset(copy, 0, sizeof(*copy));
+
+ copy->action = hook->action;
+ copy->action_data = hook->action_data;
+ isc_mem_attach(mctx, &copy->mctx);
+
+ ISC_LINK_INIT(copy, link);
+ ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link);
+}
+
+void
+ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) {
+ ns_plugins_t *plugins = NULL;
+
+ REQUIRE(listp != NULL && *listp == NULL);
+
+ plugins = isc_mem_get(mctx, sizeof(*plugins));
+ memset(plugins, 0, sizeof(*plugins));
+ ISC_LIST_INIT(*plugins);
+
+ *listp = plugins;
+}
+
+void
+ns_plugins_free(isc_mem_t *mctx, void **listp) {
+ ns_plugins_t *list = NULL;
+ ns_plugin_t *plugin = NULL, *next = NULL;
+
+ REQUIRE(listp != NULL && *listp != NULL);
+
+ list = *listp;
+ *listp = NULL;
+
+ for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) {
+ next = ISC_LIST_NEXT(plugin, link);
+ ISC_LIST_UNLINK(*list, plugin, link);
+ unload_plugin(&plugin);
+ }
+
+ isc_mem_put(mctx, list, sizeof(*list));
+}
diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h
new file mode 100644
index 0000000..7a7196f
--- /dev/null
+++ b/lib/ns/include/ns/client.h
@@ -0,0 +1,578 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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
+ * This module defines two objects, ns_client_t and ns_clientmgr_t.
+ *
+ * An ns_client_t object handles incoming DNS requests from clients
+ * on a given network interface.
+ *
+ * Each ns_client_t object can handle only one TCP connection or UDP
+ * request at a time. Therefore, several ns_client_t objects are
+ * typically created to serve each network interface, e.g., one
+ * for handling TCP requests and a few (one per CPU) for handling
+ * UDP requests.
+ *
+ * Incoming requests are classified as queries, zone transfer
+ * requests, update requests, notify requests, etc, and handed off
+ * to the appropriate request handler. When the request has been
+ * fully handled (which can be much later), the ns_client_t must be
+ * notified of this by calling one of the following functions
+ * exactly once in the context of its task:
+ * \code
+ * ns_client_send() (sending a non-error response)
+ * ns_client_sendraw() (sending a raw response)
+ * ns_client_error() (sending an error response)
+ * ns_client_drop() (sending no response, logging the reason)
+ *\endcode
+ * This will release any resources used by the request and
+ * and allow the ns_client_t to listen for the next request.
+ *
+ * A ns_clientmgr_t manages a number of ns_client_t objects.
+ * New ns_client_t objects are created by calling
+ * ns_clientmgr_createclients(). They are destroyed by
+ * destroying their manager.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/atomic.h>
+#include <isc/buffer.h>
+#include <isc/magic.h>
+#include <isc/netmgr.h>
+#include <isc/quota.h>
+#include <isc/stdtime.h>
+
+#include <dns/db.h>
+#include <dns/ecs.h>
+#include <dns/fixedname.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatatype.h>
+#include <dns/types.h>
+
+#include <ns/query.h>
+#include <ns/types.h>
+
+/***
+ *** Types
+ ***/
+
+#define NS_CLIENT_TCP_BUFFER_SIZE 65535
+#define NS_CLIENT_SEND_BUFFER_SIZE 4096
+
+/*!
+ * Client object states. Ordering is significant: higher-numbered
+ * states are generally "more active", meaning that the client can
+ * have more dynamically allocated data, outstanding events, etc.
+ * In the list below, any such properties listed for state N
+ * also apply to any state > N.
+ */
+
+typedef enum {
+ NS_CLIENTSTATE_FREED = 0,
+ /*%<
+ * The client object no longer exists.
+ */
+
+ NS_CLIENTSTATE_INACTIVE = 1,
+ /*%<
+ * The client object exists and has a task and timer.
+ * Its "query" struct and sendbuf are initialized.
+ * It has a message and OPT, both in the reset state.
+ */
+
+ NS_CLIENTSTATE_READY = 2,
+ /*%<
+ * The client object is either a TCP or a UDP one, and
+ * it is associated with a network interface. It is on the
+ * client manager's list of active clients.
+ *
+ * If it is a TCP client object, it has a TCP listener socket
+ * and an outstanding TCP listen request.
+ *
+ * If it is a UDP client object, it has a UDP listener socket
+ * and an outstanding UDP receive request.
+ */
+
+ NS_CLIENTSTATE_WORKING = 3,
+ /*%<
+ * The client object has received a request and is working
+ * on it. It has a view, and it may have any of a non-reset OPT,
+ * recursion quota, and an outstanding write request.
+ */
+
+ NS_CLIENTSTATE_RECURSING = 4,
+ /*%<
+ * The client object is recursing. It will be on the
+ * 'recursing' list.
+ */
+
+ NS_CLIENTSTATE_MAX = 5
+ /*%<
+ * Sentinel value used to indicate "no state".
+ */
+} ns_clientstate_t;
+
+typedef ISC_LIST(ns_client_t) client_list_t;
+
+/*% nameserver client manager structure */
+struct ns_clientmgr {
+ /* Unlocked. */
+ unsigned int magic;
+
+ isc_mem_t *mctx;
+ isc_mem_t *send_mctx;
+ ns_server_t *sctx;
+ isc_taskmgr_t *taskmgr;
+ isc_timermgr_t *timermgr;
+ isc_refcount_t references;
+ int tid;
+
+ /* Attached by clients, needed for e.g. recursion */
+ isc_task_t *task;
+
+ dns_aclenv_t *aclenv;
+
+ /* Lock covers the recursing list */
+ isc_mutex_t reclock;
+ client_list_t recursing; /*%< Recursing clients */
+};
+
+/*% nameserver client structure */
+struct ns_client {
+ unsigned int magic;
+ isc_mem_t *mctx;
+ int tid;
+ bool allocated; /* Do we need to free it? */
+ ns_server_t *sctx;
+ ns_clientmgr_t *manager;
+ ns_clientstate_t state;
+ int nupdates;
+ bool nodetach;
+ bool shuttingdown;
+ unsigned int attributes;
+ isc_task_t *task;
+ dns_view_t *view;
+ dns_dispatch_t *dispatch;
+ isc_nmhandle_t *handle; /* Permanent pointer to handle */
+ isc_nmhandle_t *sendhandle; /* Waiting for send callback */
+ isc_nmhandle_t *reqhandle; /* Waiting for request callback
+ (query, update, notify) */
+ isc_nmhandle_t *fetchhandle; /* Waiting for recursive fetch */
+ isc_nmhandle_t *prefetchhandle; /* Waiting for prefetch / rpzfetch */
+ isc_nmhandle_t *updatehandle; /* Waiting for update callback */
+ unsigned char *tcpbuf;
+ size_t tcpbuf_size;
+ dns_message_t *message;
+ unsigned char *sendbuf;
+ dns_rdataset_t *opt;
+ dns_ednsopt_t *ede;
+ uint16_t udpsize;
+ uint16_t extflags;
+ int16_t ednsversion; /* -1 noedns */
+ uint16_t additionaldepth;
+ void (*cleanup)(ns_client_t *);
+ ns_query_t query;
+ isc_time_t requesttime;
+ isc_stdtime_t now;
+ isc_time_t tnow;
+ dns_name_t signername; /*%< [T]SIG key name */
+ dns_name_t *signer; /*%< NULL if not valid sig */
+ bool mortal; /*%< Die after handling request */
+ isc_quota_t *recursionquota;
+
+ isc_sockaddr_t peeraddr;
+ bool peeraddr_valid;
+ isc_netaddr_t destaddr;
+ isc_sockaddr_t destsockaddr;
+
+ dns_ecs_t ecs; /*%< EDNS client subnet sent by client */
+
+ struct in6_pktinfo pktinfo;
+ /*%
+ * Information about recent FORMERR response(s), for
+ * FORMERR loop avoidance. This is separate for each
+ * client object rather than global only to avoid
+ * the need for locking.
+ */
+ struct {
+ isc_sockaddr_t addr;
+ isc_stdtime_t time;
+ dns_messageid_t id;
+ } formerrcache;
+
+ /*% Callback function to send a response when unit testing */
+ void (*sendcb)(isc_buffer_t *buf);
+
+ ISC_LINK(ns_client_t) rlink;
+ unsigned char cookie[8];
+ uint32_t expire;
+ unsigned char *keytag;
+ uint16_t keytag_len;
+
+ /*%
+ * Used to override the DNS response code in ns_client_error().
+ * If set to -1, the rcode is determined from the result code,
+ * but if set to any other value, the least significant 12
+ * bits will be used as the rcode in the response message.
+ */
+ int32_t rcode_override;
+};
+
+#define NS_CLIENT_MAGIC ISC_MAGIC('N', 'S', 'C', 'c')
+#define NS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, NS_CLIENT_MAGIC)
+
+#define NS_CLIENTATTR_TCP 0x00001
+#define NS_CLIENTATTR_RA 0x00002 /*%< Client gets recursive service */
+#define NS_CLIENTATTR_PKTINFO 0x00004 /*%< pktinfo is valid */
+#define NS_CLIENTATTR_MULTICAST 0x00008 /*%< recv'd from multicast */
+#define NS_CLIENTATTR_WANTDNSSEC 0x00010 /*%< include dnssec records */
+#define NS_CLIENTATTR_WANTNSID 0x00020 /*%< include nameserver ID */
+/* Obsolete: NS_CLIENTATTR_FILTER_AAAA 0x00040 */
+/* Obsolete: NS_CLIENTATTR_FILTER_AAAA_RC 0x00080 */
+#define NS_CLIENTATTR_WANTAD 0x00100 /*%< want AD in response if possible */
+#define NS_CLIENTATTR_WANTCOOKIE 0x00200 /*%< return a COOKIE */
+#define NS_CLIENTATTR_HAVECOOKIE 0x00400 /*%< has a valid COOKIE */
+#define NS_CLIENTATTR_WANTEXPIRE 0x00800 /*%< return seconds to expire */
+#define NS_CLIENTATTR_HAVEEXPIRE 0x01000 /*%< return seconds to expire */
+#define NS_CLIENTATTR_WANTOPT 0x02000 /*%< add opt to reply */
+#define NS_CLIENTATTR_HAVEECS 0x04000 /*%< received an ECS option */
+#define NS_CLIENTATTR_WANTPAD 0x08000 /*%< pad reply */
+#define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */
+
+#define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */
+
+/*
+ * Flag to use with the SERVFAIL cache to indicate
+ * that a query had the CD bit set.
+ */
+#define NS_FAILCACHE_CD 0x01
+
+extern atomic_uint_fast64_t ns_client_requests;
+
+/***
+ *** Functions
+ ***/
+
+/*
+ * Note! These ns_client_ routines MUST be called ONLY from the client's
+ * task in order to ensure synchronization.
+ */
+
+void
+ns_client_send(ns_client_t *client);
+/*%<
+ * Finish processing the current client request and
+ * send client->message as a response.
+ * \brief
+ * Note! These ns_client_ routines MUST be called ONLY from the client's
+ * task in order to ensure synchronization.
+ */
+
+void
+ns_client_sendraw(ns_client_t *client, dns_message_t *msg);
+/*%<
+ * Finish processing the current client request and
+ * send msg as a response using client->message->id for the id.
+ */
+
+void
+ns_client_error(ns_client_t *client, isc_result_t result);
+/*%<
+ * Finish processing the current client request and return
+ * an error response to the client. The error response
+ * will have an RCODE determined by 'result'.
+ */
+
+void
+ns_client_extendederror(ns_client_t *client, uint16_t code, const char *text);
+/*%<
+ * Set extended error with INFO-CODE <code> and EXTRA-TEXT <text>.
+ */
+
+void
+ns_client_drop(ns_client_t *client, isc_result_t result);
+/*%<
+ * Log the reason the current client request has failed; no response
+ * will be sent.
+ */
+
+bool
+ns_client_shuttingdown(ns_client_t *client);
+/*%<
+ * Return true iff the client is currently shutting down.
+ */
+
+isc_result_t
+ns_client_replace(ns_client_t *client);
+/*%<
+ * Try to replace the current client with a new one, so that the
+ * current one can go off and do some lengthy work without
+ * leaving the dispatch/socket without service.
+ */
+
+void
+ns_client_settimeout(ns_client_t *client, unsigned int seconds);
+/*%<
+ * Set a timer in the client to go off in the specified amount of time.
+ */
+
+isc_result_t
+ns_clientmgr_create(ns_server_t *sctx, isc_taskmgr_t *taskmgr,
+ isc_timermgr_t *timermgr, dns_aclenv_t *aclenv, int tid,
+ ns_clientmgr_t **managerp);
+/*%<
+ * Create a client manager.
+ */
+
+void
+ns_clientmgr_shutdown(ns_clientmgr_t *manager);
+/*%<
+ * Shutdown a client manager and all ns_client_t objects
+ * managed by it
+ */
+
+void
+ns_clientmgr_detach(ns_clientmgr_t **managerp);
+/*%<
+ * Detach from a client manager.
+ */
+
+isc_sockaddr_t *
+ns_client_getsockaddr(ns_client_t *client);
+/*%<
+ * Get the socket address of the client whose request is
+ * currently being processed.
+ */
+
+isc_sockaddr_t *
+ns_client_getdestaddr(ns_client_t *client);
+/*%<
+ * Get the destination address (server) for the request that is
+ * currently being processed.
+ */
+
+isc_result_t
+ns_client_checkaclsilent(ns_client_t *client, isc_netaddr_t *netaddr,
+ dns_acl_t *acl, bool default_allow);
+
+/*%<
+ * Convenience function for client request ACL checking.
+ *
+ * Check the current client request against 'acl'. If 'acl'
+ * is NULL, allow the request iff 'default_allow' is true.
+ * If netaddr is NULL, check the ACL against client->peeraddr;
+ * otherwise check it against netaddr.
+ *
+ * Notes:
+ *\li This is appropriate for checking allow-update,
+ * allow-query, allow-transfer, etc. It is not appropriate
+ * for checking the blackhole list because we treat positive
+ * matches as "allow" and negative matches as "deny"; in
+ * the case of the blackhole list this would be backwards.
+ *
+ * Requires:
+ *\li 'client' points to a valid client.
+ *\li 'netaddr' points to a valid address, or is NULL.
+ *\li 'acl' points to a valid ACL, or is NULL.
+ *
+ * Returns:
+ *\li ISC_R_SUCCESS if the request should be allowed
+ * \li DNS_R_REFUSED if the request should be denied
+ *\li No other return values are possible.
+ */
+
+isc_result_t
+ns_client_checkacl(ns_client_t *client, isc_sockaddr_t *sockaddr,
+ const char *opname, dns_acl_t *acl, bool default_allow,
+ int log_level);
+/*%<
+ * Like ns_client_checkaclsilent, except the outcome of the check is
+ * logged at log level 'log_level' if denied, and at debug 3 if approved.
+ * Log messages will refer to the request as an 'opname' request.
+ *
+ * Requires:
+ *\li 'client' points to a valid client.
+ *\li 'sockaddr' points to a valid address, or is NULL.
+ *\li 'acl' points to a valid ACL, or is NULL.
+ *\li 'opname' points to a null-terminated string.
+ */
+
+void
+ns_client_log(ns_client_t *client, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(5, 6);
+
+void
+ns_client_logv(ns_client_t *client, isc_logcategory_t *category,
+ isc_logmodule_t *module, int level, const char *fmt, va_list ap)
+ ISC_FORMAT_PRINTF(5, 0);
+
+void
+ns_client_aclmsg(const char *msg, const dns_name_t *name, dns_rdatatype_t type,
+ dns_rdataclass_t rdclass, char *buf, size_t len);
+
+#define NS_CLIENT_ACLMSGSIZE(x) \
+ (DNS_NAME_FORMATSIZE + DNS_RDATATYPE_FORMATSIZE + \
+ DNS_RDATACLASS_FORMATSIZE + sizeof(x) + sizeof("'/'"))
+
+void
+ns_client_recursing(ns_client_t *client);
+/*%<
+ * Add client to end of th recursing list.
+ */
+
+void
+ns_client_killoldestquery(ns_client_t *client);
+/*%<
+ * Kill the oldest recursive query (recursing list head).
+ */
+
+void
+ns_client_dumprecursing(FILE *f, ns_clientmgr_t *manager);
+/*%<
+ * Dump the outstanding recursive queries to 'f'.
+ */
+
+void
+ns_client_qnamereplace(ns_client_t *client, dns_name_t *name);
+/*%<
+ * Replace the qname.
+ */
+
+isc_result_t
+ns_client_sourceip(dns_clientinfo_t *ci, isc_sockaddr_t **addrp);
+
+isc_result_t
+ns_client_addopt(ns_client_t *client, dns_message_t *message,
+ dns_rdataset_t **opt);
+
+/*%<
+ * Get a client object from the inactive queue, or create one, as needed.
+ * (Not intended for use outside this module and associated tests.)
+ */
+
+void
+ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
+ isc_region_t *region, void *arg);
+
+/*%<
+ * Handle client requests.
+ * (Not intended for use outside this module and associated tests.)
+ */
+
+isc_result_t
+ns__client_tcpconn(isc_nmhandle_t *handle, isc_result_t result, void *arg);
+
+/*%<
+ * Called every time a TCP connection is establish. This is used for
+ * updating TCP statistics.
+ */
+
+dns_rdataset_t *
+ns_client_newrdataset(ns_client_t *client);
+
+void
+ns_client_putrdataset(ns_client_t *client, dns_rdataset_t **rdatasetp);
+/*%<
+ * Get and release temporary rdatasets in the client message;
+ * used in query.c and in plugins.
+ */
+
+isc_result_t
+ns_client_newnamebuf(ns_client_t *client);
+/*%<
+ * Allocate a name buffer for the client message.
+ */
+
+dns_name_t *
+ns_client_newname(ns_client_t *client, isc_buffer_t *dbuf, isc_buffer_t *nbuf);
+/*%<
+ * Get a temporary name for the client message.
+ */
+
+isc_buffer_t *
+ns_client_getnamebuf(ns_client_t *client);
+/*%<
+ * Get a name buffer from the pool, or allocate a new one if needed.
+ */
+
+void
+ns_client_keepname(ns_client_t *client, dns_name_t *name, isc_buffer_t *dbuf);
+/*%<
+ * Adjust buffer 'dbuf' to reflect that 'name' is using space in it,
+ * and set client attributes appropriately.
+ */
+
+void
+ns_client_releasename(ns_client_t *client, dns_name_t **namep);
+/*%<
+ * Release 'name' back to the pool of temporary names for the client
+ * message. If it is using a name buffer, relinquish its exclusive
+ * rights on the buffer.
+ */
+
+isc_result_t
+ns_client_newdbversion(ns_client_t *client, unsigned int n);
+/*%<
+ * Allocate 'n' new database versions for use by client queries.
+ */
+
+ns_dbversion_t *
+ns_client_getdbversion(ns_client_t *client);
+/*%<
+ * Get a free database version for use by a client query, allocating
+ * a new one if necessary.
+ */
+
+ns_dbversion_t *
+ns_client_findversion(ns_client_t *client, dns_db_t *db);
+/*%<
+ * Find the correct database version to use with a client query.
+ * If we have already done a query related to the database 'db',
+ * make sure subsequent queries are from the same version;
+ * otherwise, take a database version from the list of dbversions
+ * allocated by ns_client_newdbversion().
+ */
+
+isc_result_t
+ns__client_setup(ns_client_t *client, ns_clientmgr_t *manager, bool new);
+/*%<
+ * Perform initial setup of an allocated client.
+ */
+
+void
+ns__client_reset_cb(void *client0);
+/*%<
+ * Reset the client object so that it can be reused.
+ */
+
+void
+ns__client_put_cb(void *client0);
+/*%<
+ * Free all resources allocated to this client object, so that
+ * it can be freed.
+ */
diff --git a/lib/ns/include/ns/events.h b/lib/ns/include/ns/events.h
new file mode 100644
index 0000000..b651827
--- /dev/null
+++ b/lib/ns/include/ns/events.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
+
+#include <isc/eventclass.h>
+
+/*! \file ns/events.h
+ * \brief
+ * Registry of NS event numbers.
+ */
+
+#define NS_EVENT_CLIENTCONTROL (ISC_EVENTCLASS_NS + 0)
+#define NS_EVENT_HOOKASYNCDONE (ISC_EVENTCLASS_NS + 1)
+#define NS_EVENT_IFSCAN (ISC_EVENTCLASS_NS + 2)
diff --git a/lib/ns/include/ns/hooks.h b/lib/ns/include/ns/hooks.h
new file mode 100644
index 0000000..cb146ff
--- /dev/null
+++ b/lib/ns/include/ns/hooks.h
@@ -0,0 +1,612 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/event.h>
+#include <isc/list.h>
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/result.h>
+#include <isc/task.h>
+
+#include <dns/rdatatype.h>
+
+#include <ns/client.h>
+#include <ns/query.h>
+/*
+ * "Hooks" are a mechanism to call a defined function or set of functions once
+ * a certain place in code is reached. Hook actions can inspect and alter the
+ * state of an ongoing process, allowing processing to continue afterward or
+ * triggering an early return.
+ *
+ * Currently hooks are used in two ways: in plugins, which use them to
+ * add functionality to query processing, and in the unit tests for libns,
+ * where they are used to inspect state before and after certain functions have
+ * run.
+ *
+ * Both of these uses are limited to libns, so hooks are currently defined in
+ * the ns/hooks.h header file, and hook-related macro and function names are
+ * prefixed with `NS_` and `ns_`. However, the design is fairly generic and
+ * could be repurposed for general use, e.g. as part of libisc, after some
+ * further customization.
+ *
+ * Hooks are created by defining a hook point identifier in the ns_hookpoint_t
+ * enum below, and placing a special call at a corresponding location in the
+ * code which invokes the action(s) for that hook; there are two such special
+ * calls currently implemented, namely the CALL_HOOK() and CALL_HOOK_NORETURN()
+ * macros in query.c. The former macro contains a "goto cleanup" statement
+ * which is inlined into the function into which the hook has been inserted;
+ * this enables the hook action to cause the calling function to return from
+ * the hook insertion point. For functions returning isc_result_t, if a hook
+ * action intends to cause a return at hook insertion point, it also has to set
+ * the value to be returned by the calling function.
+ *
+ * A hook table is an array (indexed by the value of the hook point identifier)
+ * in which each cell contains a linked list of structures, each of which
+ * contains a function pointer to a hook action and a pointer to data which is
+ * to be passed to the action function when it is called.
+ *
+ * Each view has its own separate hook table, populated by loading plugin
+ * modules specified in the "plugin" statements in named.conf. There is also a
+ * special, global hook table (ns__hook_table) that is only used by libns unit
+ * tests and whose existence can be safely ignored by plugin modules.
+ *
+ * Hook actions are functions which:
+ *
+ * - return an ns_hookresult_t value:
+ * - if NS_HOOK_RETURN is returned by the hook action, the function
+ * into which the hook is inserted will return and no further hook
+ * actions at the same hook point will be invoked,
+ * - if NS_HOOK_CONTINUE is returned by the hook action and there are
+ * further hook actions set up at the same hook point, they will be
+ * processed; if NS_HOOK_CONTINUE is returned and there are no
+ * further hook actions set up at the same hook point, execution of
+ * the function into which the hook has been inserted will be
+ * resumed.
+ *
+ * - accept three pointers as arguments:
+ * - a pointer specified by the special call at the hook insertion point,
+ * - a pointer specified upon inserting the action into the hook table,
+ * - a pointer to an isc_result_t value which will be returned by the
+ * function into which the hook is inserted if the action returns
+ * NS_HOOK_RETURN.
+ *
+ * In order for a hook action to be called for a given hook, a pointer to that
+ * action function (along with an optional pointer to action-specific data) has
+ * to be inserted into the relevant hook table entry for that hook using an
+ * ns_hook_add() call. If multiple actions are set up at a single hook point
+ * (e.g. by multiple plugin modules), they are processed in FIFO order, that is
+ * they are performed in the same order in which their relevant ns_hook_add()
+ * calls were issued. Since the configuration is loaded from a single thread,
+ * this means that multiple actions at a single hook point are determined by
+ * the order in which the relevant plugin modules were declared in the
+ * configuration file(s). The hook API currently does not support changing
+ * this order.
+ *
+ * As an example, consider the following hypothetical function in query.c:
+ *
+ * ----------------------------------------------------------------------------
+ * static isc_result_t
+ * query_foo(query_ctx_t *qctx) {
+ * isc_result_t result;
+ *
+ * CALL_HOOK(NS_QUERY_FOO_BEGIN, qctx);
+ *
+ * ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
+ * ISC_LOG_DEBUG(99), "Lorem ipsum dolor sit amet...");
+ *
+ * result = ISC_R_COMPLETE;
+ *
+ * cleanup:
+ * return (result);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * and the following hook action:
+ *
+ * ----------------------------------------------------------------------------
+ * static ns_hookresult_t
+ * cause_failure(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * UNUSED(hook_data);
+ * UNUSED(action_data);
+ *
+ * *resultp = ISC_R_FAILURE;
+ *
+ * return (NS_HOOK_RETURN);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * If this hook action was installed in the hook table using:
+ *
+ * ----------------------------------------------------------------------------
+ * const ns_hook_t foo_fail = {
+ * .action = cause_failure,
+ * };
+ *
+ * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_fail);
+ * ----------------------------------------------------------------------------
+ *
+ * then query_foo() would return ISC_R_FAILURE every time it is called due
+ * to the cause_failure() hook action returning NS_HOOK_RETURN and setting
+ * '*resultp' to ISC_R_FAILURE. query_foo() would also never log the
+ * "Lorem ipsum dolor sit amet..." message.
+ *
+ * Consider a different hook action:
+ *
+ * ----------------------------------------------------------------------------
+ * static ns_hookresult_t
+ * log_qtype(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * query_ctx_t *qctx = (query_ctx_t *)hook_data;
+ * FILE *stream = (FILE *)action_data;
+ *
+ * UNUSED(resultp);
+ *
+ * fprintf(stream, "QTYPE=%u\n", qctx->qtype);
+ *
+ * return (NS_HOOK_CONTINUE);
+ * }
+ * ----------------------------------------------------------------------------
+ *
+ * If this hook action was installed in the hook table instead of
+ * cause_failure(), using:
+ *
+ * ----------------------------------------------------------------------------
+ * const ns_hook_t foo_log_qtype = {
+ * .action = log_qtype,
+ * .action_data = stderr,
+ * };
+ *
+ * ns_hook_add(..., NS_QUERY_FOO_BEGIN, &foo_log_qtype);
+ * ----------------------------------------------------------------------------
+ *
+ * then the QTYPE stored in the query context passed to query_foo() would be
+ * logged to stderr upon each call to that function; 'qctx' would be passed to
+ * the hook action in 'hook_data' since it is specified in the CALL_HOOK() call
+ * inside query_foo() while stderr would be passed to the hook action in
+ * 'action_data' since it is specified in the ns_hook_t structure passed to
+ * ns_hook_add(). As the hook action returns NS_HOOK_CONTINUE,
+ * query_foo() would also be logging the "Lorem ipsum dolor sit amet..."
+ * message before returning ISC_R_COMPLETE.
+ *
+ * ASYNCHRONOUS EVENT HANDLING IN QUERY HOOKS
+ *
+ * Usually a hook action works synchronously; it completes some particular
+ * job in the middle of query processing, thus blocking the caller (and the
+ * worker thread handling the query). But sometimes an action can be time
+ * consuming and the blocking behavior may not be acceptable. For example,
+ * a hook may need to send some kind of query (like a DB lookup) to an
+ * external backend server and wait for the response to complete the hook's
+ * action. Depending on the network condition, the external server's load,
+ * etc, it may take several seconds or more.
+ *
+ * In order to handle such a situation, a hook action can start an
+ * asynchronous event by calling ns_query_hookasync(). This is similar
+ * to ns_query_recurse(), but more generic. ns_query_hookasync() will
+ * call the 'runasync' function with a specified 'arg' (both passed to
+ * ns_query_hookasync()) and a set of task and associated event arguments
+ * to be called to resume query handling upon completion of the
+ * asynchronous event.
+ *
+ * The implementation of 'runasync' is assumed to allocate and build an
+ * instance of ns_hook_resevent_t whose action, arg, and task are set to
+ * the passed values from ns_query_hookasync(). Other fields of
+ * ns_hook_resevent_t must be correctly set in the hook implementation
+ * by the time it's sent to the specified task:
+ *
+ * - hookpoint: the point from which the query handling should be resumed
+ * (which should usually be the hook point that triggered the asynchronous
+ * event).
+ * - origresult: the result code passed to the hook action that triggers the
+ * asynchronous event through the 'resultp' pointer. Some hook points need
+ * this value to correctly resume the query handling.
+ * - saved_qctx: the 'qctx' passed to 'runasync'. This holds some
+ * intermediate data for resolving the query, and will be used to resume the
+ * query handling. The 'runasync' implementation must not modify it.
+ *
+ * The hook implementation should somehow maintain the created event
+ * instance so that it can eventually send the event.
+ *
+ * 'runasync' then creates an instance of ns_hookasync_t with specifying its
+ * own cancel and destroy function, and returns it to ns_query_hookasync()
+ * in the passed pointer.
+ *
+ * On return from ns_query_hookasync(), the hook action MUST return
+ * NS_HOOK_RETURN to suspend the query handling.
+ *
+ * On the completion of the asynchronous event, the hook implementation is
+ * supposed to send the resumeevent to the corresponding task. The query
+ * module resumes the query handling so that the hook action of the
+ * specified hook point will be called, skipping some intermediate query
+ * handling steps. So, typically, the same hook action will be called
+ * twice. The hook implementation must somehow remember the context, and
+ * handle the second call to complete its action using the result of the
+ * asynchronous event.
+ *
+ * Example: assume the following hook-specific structure to manage
+ * asynchronous events:
+ *
+ * typedef struct hookstate {
+ * bool async;
+ * ns_hook_resevent_t *rev
+ * ns_hookpoint_t hookpoint;
+ * isc_result_t origresult;
+ * } hookstate_t;
+ *
+ * 'async' is supposed to be true if and only if hook-triggered
+ * asynchronous processing is taking place.
+ *
+ * A hook action that uses an asynchronous event would look something
+ * like this:
+ *
+ * hook_recurse(void *hook_data, void *action_data, isc_result_t *resultp) {
+ * hookstate_t *state = somehow_retrieve_from(action_data);
+ * if (state->async) {
+ * // just resumed from an asynchronous hook action.
+ * // complete the hook's action using the result of the
+ * // internal asynchronous event.
+ * state->async = false;
+ * return (NS_HOOK_CONTINUE);
+ * }
+ *
+ * // Initial call to the hook action. Start the internal
+ * // asynchronous event, and have the query module suspend
+ * // its own handling by returning NS_HOOK_RETURN.
+ * state->hookpoint = ...; // would be hook point for this hook
+ * state->origresult = *resultp;
+ * ns_query_hookasync(hook_data, runasync, state);
+ * state->async = true;
+ * return (NS_HOOK_RETURN);
+ * }
+ *
+ * And the 'runasync' function would be something like this:
+ *
+ * static isc_result_t
+ * runasync(query_ctx_t *qctx, void *arg, isc_taskaction_t action,
+ * void *evarg, isc_task_t *task, ns_hookasync_t **ctxp) {
+ * hookstate_t *state = arg;
+ * ns_hook_resevent_t *rev = isc_event_allocate(
+ * mctx, task, NS_EVENT_HOOKASYNCDONE, action, evarg,
+ * sizeof(*rev));
+ * ns_hookasync_t *ctx = isc_mem_get(mctx, sizeof(*ctx));
+ *
+ * *ctx = (ns_hookasync_t){ .private = NULL };
+ * isc_mem_attach(mctx, &ctx->mctx);
+ * ctx->cancel = ...; // set the cancel function, which cancels the
+ * // internal asynchronous event (if necessary).
+ * // it should eventually result in sending
+ * // the 'rev' event to the calling task.
+ * ctx->destroy = ...; // set the destroy function, which frees 'ctx'
+ *
+ * rev->hookpoint = state->hookpoint;
+ * rev->origresult = state->origresult;
+ * rev->saved_qctx = qctx;
+ * rev->ctx = ctx;
+ *
+ * state->rev = rev; // store the resume event so we can send it later
+ *
+ * // initiate some asynchronous process here - for example, a
+ * // recursive fetch.
+ *
+ * *ctxp = ctx;
+ * return (ISC_R_SUCCESS);
+ * }
+ *
+ * Finally, in the completion handler for the asynchronous process, we
+ * need to send a resumption event so that query processing can resume.
+ * For example, the completion handler might call this function:
+ *
+ * static void
+ * asyncproc_done(hookstate_t *state) {
+ * isc_event_t *ev = (isc_event_t *)state->rev;
+ * isc_task_send(ev->ev_sender, &ev);
+ * }
+ *
+ * Caveats:
+ * - On resuming from a hook-initiated asynchronous process, code in
+ * the query module before the hook point needs to be exercised.
+ * So if this part has side effects, it's possible that the resuming
+ * doesn't work well. Currently, NS_QUERY_RESPOND_ANY_FOUND is
+ * explicitly prohibited to be used as the resume point.
+ * - In general, hooks other than those called at the beginning of the
+ * caller function may not work safely with asynchronous processing for
+ * the reason stated in the previous bullet. For example, a hook action
+ * for NS_QUERY_DONE_SEND may not be able to start an asychronous
+ * function safely.
+ * - Hook-triggered asynchronous processing is not allowed to be running
+ * while the standard DNS recursive fetch is taking place (starting
+ * from a call to dns_resolver_createfetch()), as the two would be
+ * using some of the same context resources. For this reason the
+ * NS_QUERY_NOTFOUND_RECURSE and NS_QUERY_ZEROTTL_RECURSE hook points
+ * are explicitly prohibited from being used for asynchronous hook
+ * actions.
+ * - Specifying multiple hook actions for the same hook point at the
+ * same time may cause problems, as resumption from one hook action
+ * could cause another hook to be called twice unintentionally.
+ * It's generally not safe to assume such a use case works,
+ * especially if the hooks are developed independently. (Note that
+ * that's not necessarily specific to the use of asynchronous hook
+ * actions. As long as hook actions have side effects, including
+ * modifying the internal query state, it's not guaranteed safe
+ * to use multiple independent hooks at the same time.)
+ */
+
+/*!
+ * Currently-defined hook points. So long as these are unique, the order in
+ * which they are declared is unimportant, but it currently matches the
+ * order in which they are referenced in query.c.
+ */
+typedef enum {
+ /* hookpoints from query.c */
+ NS_QUERY_QCTX_INITIALIZED,
+ NS_QUERY_QCTX_DESTROYED,
+ NS_QUERY_SETUP,
+ NS_QUERY_START_BEGIN,
+ NS_QUERY_LOOKUP_BEGIN,
+ NS_QUERY_RESUME_BEGIN,
+ NS_QUERY_RESUME_RESTORED,
+ NS_QUERY_GOT_ANSWER_BEGIN,
+ NS_QUERY_RESPOND_ANY_BEGIN,
+ NS_QUERY_RESPOND_ANY_FOUND,
+ NS_QUERY_ADDANSWER_BEGIN,
+ NS_QUERY_RESPOND_BEGIN,
+ NS_QUERY_NOTFOUND_BEGIN,
+ NS_QUERY_NOTFOUND_RECURSE,
+ NS_QUERY_PREP_DELEGATION_BEGIN,
+ NS_QUERY_ZONE_DELEGATION_BEGIN,
+ NS_QUERY_DELEGATION_BEGIN,
+ NS_QUERY_DELEGATION_RECURSE_BEGIN,
+ NS_QUERY_NODATA_BEGIN,
+ NS_QUERY_NXDOMAIN_BEGIN,
+ NS_QUERY_NCACHE_BEGIN,
+ NS_QUERY_ZEROTTL_RECURSE,
+ NS_QUERY_CNAME_BEGIN,
+ NS_QUERY_DNAME_BEGIN,
+ NS_QUERY_PREP_RESPONSE_BEGIN,
+ NS_QUERY_DONE_BEGIN,
+ NS_QUERY_DONE_SEND,
+
+ /* XXX other files could be added later */
+
+ NS_HOOKPOINTS_COUNT /* MUST BE LAST */
+} ns_hookpoint_t;
+
+/*
+ * Returned by a hook action to indicate how to proceed after it has
+ * been called: continue processing, or return immediately.
+ */
+typedef enum {
+ NS_HOOK_CONTINUE,
+ NS_HOOK_RETURN,
+} ns_hookresult_t;
+
+typedef ns_hookresult_t (*ns_hook_action_t)(void *arg, void *data,
+ isc_result_t *resultp);
+
+typedef struct ns_hook {
+ isc_mem_t *mctx;
+ ns_hook_action_t action;
+ void *action_data;
+ ISC_LINK(struct ns_hook) link;
+} ns_hook_t;
+
+typedef ISC_LIST(ns_hook_t) ns_hooklist_t;
+typedef ns_hooklist_t ns_hooktable_t[NS_HOOKPOINTS_COUNT];
+
+/*%
+ * ns__hook_table is a global hook table, which is used if view->hooktable
+ * is NULL. It's intended only for use by unit tests.
+ */
+extern ns_hooktable_t *ns__hook_table;
+
+typedef void (*ns_hook_cancelasync_t)(ns_hookasync_t *);
+typedef void (*ns_hook_destroyasync_t)(ns_hookasync_t **);
+
+/*%
+ * Context for a hook-initiated asynchronous process. This works
+ * similarly to dns_fetch_t.
+ */
+struct ns_hookasync {
+ isc_mem_t *mctx;
+
+ /*
+ * The following two are equivalent to dns_resolver_cancelfetch and
+ * dns_resolver_destroyfetch, respectively, but specified as function
+ * pointers since they can be hook-specific.
+ */
+ ns_hook_cancelasync_t cancel;
+ ns_hook_destroyasync_t destroy;
+
+ void *private; /* hook-specific data */
+};
+
+/*
+ * isc_event to be sent on the completion of a hook-initiated asyncronous
+ * process, similar to dns_fetchevent_t.
+ */
+typedef struct ns_hook_resevent {
+ ISC_EVENT_COMMON(struct ns_hook_resevent);
+ ns_hookasync_t *ctx; /* asynchronous processing context */
+ ns_hookpoint_t hookpoint; /* hook point from which to resume */
+ isc_result_t origresult; /* result code at the point of call to hook */
+ query_ctx_t *saved_qctx; /* qctx at the point of call to hook */
+} ns_hook_resevent_t;
+
+/*
+ * Plugin API version
+ *
+ * When the API changes, increment NS_PLUGIN_VERSION. If the
+ * change is backward-compatible (e.g., adding a new function call
+ * but not changing or removing an old one), increment NS_PLUGIN_AGE
+ * as well; if not, set NS_PLUGIN_AGE to 0.
+ */
+#ifndef NS_PLUGIN_VERSION
+#define NS_PLUGIN_VERSION 1
+#define NS_PLUGIN_AGE 0
+#endif /* ifndef NS_PLUGIN_VERSION */
+
+typedef isc_result_t
+ns_plugin_register_t(const char *parameters, const void *cfg, const char *file,
+ unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
+ void *actx, ns_hooktable_t *hooktable, void **instp);
+/*%<
+ * Called when registering a new plugin.
+ *
+ * 'parameters' contains the plugin configuration text.
+ *
+ * '*instp' will be set to the module instance handle if the function
+ * is successful.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS
+ *\li #ISC_R_NOMEMORY
+ *\li Other errors are possible
+ */
+
+typedef void
+ns_plugin_destroy_t(void **instp);
+/*%<
+ * Destroy a plugin instance.
+ *
+ * '*instp' must be set to NULL by the function before it returns.
+ */
+
+typedef isc_result_t
+ns_plugin_check_t(const char *parameters, const void *cfg, const char *file,
+ unsigned long line, isc_mem_t *mctx, isc_log_t *lctx,
+ void *actx);
+/*%<
+ * Check the validity of 'parameters'.
+ */
+
+typedef int
+ns_plugin_version_t(void);
+/*%<
+ * Return the API version number a plugin was compiled with.
+ *
+ * If the returned version number is no greater than
+ * NS_PLUGIN_VERSION, and no less than NS_PLUGIN_VERSION - NS_PLUGIN_AGE,
+ * then the module is API-compatible with named.
+ */
+
+/*%
+ * Prototypes for API functions to be defined in each module.
+ */
+ns_plugin_check_t plugin_check;
+ns_plugin_destroy_t plugin_destroy;
+ns_plugin_register_t plugin_register;
+ns_plugin_version_t plugin_version;
+
+isc_result_t
+ns_plugin_expandpath(const char *src, char *dst, size_t dstsize);
+/*%<
+ * Prepare the plugin location to be passed to dlopen() based on the plugin
+ * path or filename found in the configuration file ('src'). Store the result
+ * in 'dst', which is 'dstsize' bytes large.
+ *
+ * On Unix systems, two classes of 'src' are recognized:
+ *
+ * - If 'src' is an absolute or relative path, it will be copied to 'dst'
+ * verbatim.
+ *
+ * - If 'src' is a filename (i.e. does not contain a path separator), the
+ * path to the directory into which named plugins are installed will be
+ * prepended to it and the result will be stored in 'dst'.
+ *
+ * Returns:
+ *\li #ISC_R_SUCCESS Success
+ *\li #ISC_R_NOSPACE 'dst' is not large enough to hold the output string
+ *\li Other result snprintf() returned a negative value
+ */
+
+isc_result_t
+ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line,
+ isc_mem_t *mctx, isc_log_t *lctx, void *actx,
+ dns_view_t *view);
+/*%<
+ * Load the plugin module specified from the file 'modpath', and
+ * register an instance using 'parameters'.
+ *
+ * 'cfg_file' and 'cfg_line' specify the location of the plugin
+ * declaration in the configuration file.
+ *
+ * 'cfg' and 'actx' are the configuration context and ACL configuration
+ * context, respectively; they are passed as void * here in order to
+ * prevent this library from having a dependency on libisccfg).
+ *
+ * 'instp' will be left pointing to the instance of the plugin
+ * created by the module's plugin_register function.
+ */
+
+isc_result_t
+ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
+ const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
+ isc_log_t *lctx, void *actx);
+/*%<
+ * Open the plugin module at 'modpath' and check the validity of
+ * 'parameters', logging any errors or warnings found, then
+ * close it without configuring it.
+ */
+
+void
+ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp);
+/*%<
+ * Create and initialize a plugin list.
+ */
+
+void
+ns_plugins_free(isc_mem_t *mctx, void **listp);
+/*%<
+ * Close each plugin module in a plugin list, then free the list object.
+ */
+
+void
+ns_hooktable_free(isc_mem_t *mctx, void **tablep);
+/*%<
+ * Free a hook table.
+ */
+
+void
+ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
+ ns_hookpoint_t hookpoint, const ns_hook_t *hook);
+/*%<
+ * Allocate (using memory context 'mctx') a copy of the 'hook' structure
+ * describing a hook action and append it to the list of hooks at 'hookpoint'
+ * in 'hooktable'.
+ *
+ * Requires:
+ *\li 'hooktable' is not NULL
+ *
+ *\li 'mctx' is not NULL
+ *
+ *\li 'hookpoint' is less than NS_QUERY_HOOKS_COUNT
+ *
+ *\li 'hook' is not NULL
+ */
+
+void
+ns_hooktable_init(ns_hooktable_t *hooktable);
+/*%<
+ * Initialize a hook table.
+ */
+
+isc_result_t
+ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep);
+/*%<
+ * Allocate and initialize a hook table.
+ */
diff --git a/lib/ns/include/ns/interfacemgr.h b/lib/ns/include/ns/interfacemgr.h
new file mode 100644
index 0000000..028f86d
--- /dev/null
+++ b/lib/ns/include/ns/interfacemgr.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
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * The interface manager monitors the operating system's list
+ * of network interfaces, creating and destroying listeners
+ * as needed.
+ *
+ * Reliability:
+ *\li No impact expected.
+ *
+ * Resources:
+ *
+ * Security:
+ * \li The server will only be able to bind to the DNS port on
+ * newly discovered interfaces if it is running as root.
+ *
+ * Standards:
+ *\li The API for scanning varies greatly among operating systems.
+ * This module attempts to hide the differences.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/refcount.h>
+#include <isc/result.h>
+
+#include <dns/geoip.h>
+
+#include <ns/listenlist.h>
+#include <ns/types.h>
+
+/***
+ *** Types
+ ***/
+
+#define IFACE_MAGIC ISC_MAGIC('I', ':', '-', ')')
+#define NS_INTERFACE_VALID(t) ISC_MAGIC_VALID(t, IFACE_MAGIC)
+
+#define NS_INTERFACEFLAG_ANYADDR 0x01U /*%< bound to "any" address */
+#define NS_INTERFACEFLAG_LISTENING 0x02U /*%< listening */
+#define MAX_UDP_DISPATCH \
+ 128 /*%< Maximum number of UDP dispatchers \
+ * to start per interface */
+/*% The nameserver interface structure */
+struct ns_interface {
+ unsigned int magic; /*%< Magic number. */
+ ns_interfacemgr_t *mgr; /*%< Interface manager. */
+ isc_mutex_t lock;
+ unsigned int generation; /*%< Generation number. */
+ isc_sockaddr_t addr; /*%< Address and port. */
+ unsigned int flags; /*%< Interface flags */
+ char name[32]; /*%< Null terminated. */
+ isc_nmsocket_t *udplistensocket;
+ isc_nmsocket_t *tcplistensocket;
+ isc_nmsocket_t *http_listensocket;
+ isc_nmsocket_t *http_secure_listensocket;
+ isc_quota_t *http_quota;
+ isc_refcount_t ntcpaccepting; /*%< Number of clients
+ * ready to accept new
+ * TCP connections on this
+ * interface */
+ isc_refcount_t ntcpactive; /*%< Number of clients
+ * servicing TCP queries
+ * (whether accepting or
+ * connected) */
+ ns_clientmgr_t *clientmgr; /*%< Client manager. */
+ ISC_LINK(ns_interface_t) link;
+};
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx,
+ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr,
+ isc_nm_t *nm, dns_dispatchmgr_t *dispatchmgr,
+ isc_task_t *task, dns_geoip_databases_t *geoip,
+ int ncpus, bool scan, ns_interfacemgr_t **mgrp);
+/*%<
+ * Create a new interface manager.
+ *
+ * Initially, the new manager will not listen on any interfaces.
+ * Call ns_interfacemgr_setlistenon() and/or ns_interfacemgr_setlistenon6()
+ * to set nonempty listen-on lists.
+ */
+
+void
+ns_interfacemgr_attach(ns_interfacemgr_t *source, ns_interfacemgr_t **target);
+
+void
+ns_interfacemgr_detach(ns_interfacemgr_t **targetp);
+
+void
+ns_interfacemgr_shutdown(ns_interfacemgr_t *mgr);
+
+void
+ns_interfacemgr_setbacklog(ns_interfacemgr_t *mgr, int backlog);
+/*%<
+ * Set the size of the listen() backlog queue.
+ */
+
+bool
+ns_interfacemgr_islistening(ns_interfacemgr_t *mgr);
+/*%<
+ * Return if the manager is listening on any interface. It can be called
+ * after a scan or adjust.
+ */
+
+isc_result_t
+ns_interfacemgr_scan(ns_interfacemgr_t *mgr, bool verbose, bool config);
+/*%<
+ * Scan the operatings system's list of network interfaces
+ * and create listeners when new interfaces are discovered.
+ * Shut down the sockets for interfaces that go away.
+ *
+ * When 'config' is true, also shut down and recreate any existing TLS and HTTPS
+ * interfaces in order to use their new configuration.
+ *
+ * This should be called once on server startup and then
+ * periodically according to the 'interface-interval' option
+ * in named.conf.
+ */
+
+void
+ns_interfacemgr_setlistenon4(ns_interfacemgr_t *mgr, ns_listenlist_t *value);
+/*%<
+ * Set the IPv4 "listen-on" list of 'mgr' to 'value'.
+ * The previous IPv4 listen-on list is freed.
+ */
+
+void
+ns_interfacemgr_setlistenon6(ns_interfacemgr_t *mgr, ns_listenlist_t *value);
+/*%<
+ * Set the IPv6 "listen-on" list of 'mgr' to 'value'.
+ * The previous IPv6 listen-on list is freed.
+ */
+
+dns_aclenv_t *
+ns_interfacemgr_getaclenv(ns_interfacemgr_t *mgr);
+
+void
+ns_interface_shutdown(ns_interface_t *ifp);
+/*%<
+ * Stop listening for queries on interface 'ifp'.
+ * May safely be called multiple times.
+ */
+
+void
+ns_interfacemgr_dumprecursing(FILE *f, ns_interfacemgr_t *mgr);
+
+bool
+ns_interfacemgr_listeningon(ns_interfacemgr_t *mgr, const isc_sockaddr_t *addr);
+
+ns_server_t *
+ns_interfacemgr_getserver(ns_interfacemgr_t *mgr);
+/*%<
+ * Returns the ns_server object associated with the interface manager.
+ */
+
+ns_clientmgr_t *
+ns_interfacemgr_getclientmgr(ns_interfacemgr_t *mgr);
+/*%<
+ *
+ * Returns the client manager for the current worker thread.
+ * (This cannot be run from outside a network manager thread.)
+ */
diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h
new file mode 100644
index 0000000..5d0a19b
--- /dev/null
+++ b/lib/ns/include/ns/listenlist.h
@@ -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.
+ */
+
+#pragma once
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * "Listen lists", as in the "listen-on" configuration statement.
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <stdbool.h>
+
+#include <isc/net.h>
+#include <isc/tls.h>
+
+#include <dns/types.h>
+
+/***
+ *** Types
+ ***/
+
+typedef struct ns_listenelt ns_listenelt_t;
+typedef struct ns_listenlist ns_listenlist_t;
+
+struct ns_listenelt {
+ isc_mem_t *mctx;
+ in_port_t port;
+ bool is_http;
+ dns_acl_t *acl;
+ isc_tlsctx_t *sslctx;
+ isc_tlsctx_cache_t *sslctx_cache;
+ char **http_endpoints;
+ size_t http_endpoints_number;
+ uint32_t http_max_clients;
+ uint32_t max_concurrent_streams;
+ ISC_LINK(ns_listenelt_t) link;
+};
+
+struct ns_listenlist {
+ isc_mem_t *mctx;
+ int refcount;
+ ISC_LIST(ns_listenelt_t) elts;
+};
+
+typedef struct ns_listen_tls_params {
+ const char *name;
+ const char *key;
+ const char *cert;
+ const char *ca_file;
+ uint32_t protocols;
+ const char *dhparam_file;
+ const char *ciphers;
+ bool prefer_server_ciphers;
+ bool prefer_server_ciphers_set;
+ bool session_tickets;
+ bool session_tickets_set;
+} ns_listen_tls_params_t;
+
+/***
+ *** Functions
+ ***/
+
+isc_result_t
+ns_listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
+ const uint16_t family, bool tls,
+ const ns_listen_tls_params_t *tls_params,
+ isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target);
+/*%<
+ * Create a listen-on list element.
+ *
+ * Requires:
+ * \li 'targetp' is a valid pointer to a pointer containing 'NULL';
+ * \li 'tls_params' is a valid, non-'NULL' pointer if 'tls' equals 'true'.
+ * \li 'tlsctx_cache' is a valid, non-'NULL' pointer if 'tls' equals 'true'.
+ */
+
+isc_result_t
+ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl,
+ const uint16_t family, bool tls,
+ const ns_listen_tls_params_t *tls_params,
+ isc_tlsctx_cache_t *tlsctx_cache, char **endpoints,
+ size_t nendpoints, const uint32_t max_clients,
+ const uint32_t max_streams, ns_listenelt_t **target);
+/*%<
+ * Create a listen-on list element for HTTP(S).
+ */
+
+void
+ns_listenelt_destroy(ns_listenelt_t *elt);
+/*%<
+ * Destroy a listen-on list element.
+ */
+
+isc_result_t
+ns_listenlist_create(isc_mem_t *mctx, ns_listenlist_t **target);
+/*%<
+ * Create a new, empty listen-on list.
+ */
+
+void
+ns_listenlist_attach(ns_listenlist_t *source, ns_listenlist_t **target);
+/*%<
+ * Attach '*target' to '*source'.
+ */
+
+void
+ns_listenlist_detach(ns_listenlist_t **listp);
+/*%<
+ * Detach 'listp'.
+ */
+
+isc_result_t
+ns_listenlist_default(isc_mem_t *mctx, in_port_t port, bool enabled,
+ const uint16_t family, ns_listenlist_t **target);
+/*%<
+ * Create a listen-on list with default contents, matching
+ * all addresses with port 'port' (if 'enabled' is true),
+ * or no addresses (if 'enabled' is false).
+ */
diff --git a/lib/ns/include/ns/log.h b/lib/ns/include/ns/log.h
new file mode 100644
index 0000000..3ade016
--- /dev/null
+++ b/lib/ns/include/ns/log.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 */
+
+#include <isc/log.h>
+#include <isc/types.h>
+
+extern isc_log_t *ns_lctx;
+extern isc_logcategory_t ns_categories[];
+extern isc_logmodule_t ns_modules[];
+
+#define NS_LOGCATEGORY_CLIENT (&ns_categories[0])
+#define NS_LOGCATEGORY_NETWORK (&ns_categories[1])
+#define NS_LOGCATEGORY_UPDATE (&ns_categories[2])
+#define NS_LOGCATEGORY_QUERIES (&ns_categories[3])
+#define NS_LOGCATEGORY_UPDATE_SECURITY (&ns_categories[4])
+#define NS_LOGCATEGORY_QUERY_ERRORS (&ns_categories[5])
+#define NS_LOGCATEGORY_TAT (&ns_categories[6])
+#define NS_LOGCATEGORY_SERVE_STALE (&ns_categories[7])
+
+/*
+ * Backwards compatibility.
+ */
+#define NS_LOGCATEGORY_GENERAL ISC_LOGCATEGORY_GENERAL
+
+#define NS_LOGMODULE_CLIENT (&ns_modules[0])
+#define NS_LOGMODULE_QUERY (&ns_modules[1])
+#define NS_LOGMODULE_INTERFACEMGR (&ns_modules[2])
+#define NS_LOGMODULE_UPDATE (&ns_modules[3])
+#define NS_LOGMODULE_XFER_IN (&ns_modules[4])
+#define NS_LOGMODULE_XFER_OUT (&ns_modules[5])
+#define NS_LOGMODULE_NOTIFY (&ns_modules[6])
+#define NS_LOGMODULE_HOOKS (&ns_modules[7])
+
+void
+ns_log_init(isc_log_t *lctx);
+/*%<
+ * Make the libns categories and modules available for use with the
+ * ISC logging library.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ *
+ *\li ns_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
+ns_log_setcontext(isc_log_t *lctx);
+/*%<
+ * Make the libns library use the provided context for logging internal
+ * messages.
+ *
+ * Requires:
+ *\li lctx is a valid logging context.
+ */
diff --git a/lib/ns/include/ns/notify.h b/lib/ns/include/ns/notify.h
new file mode 100644
index 0000000..b7e6f7c
--- /dev/null
+++ b/lib/ns/include/ns/notify.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <ns/client.h>
+
+/***
+ *** Module Info
+ ***/
+
+/*! \file
+ * \brief
+ * RFC1996
+ * A Mechanism for Prompt Notification of Zone Changes (DNS NOTIFY)
+ */
+
+/***
+ *** Functions.
+ ***/
+
+void
+ns_notify_start(ns_client_t *client, isc_nmhandle_t *handle);
+
+/*%<
+ * Examines the incoming message to determine appropriate zone.
+ * Returns FORMERR if there is not exactly one question.
+ * Returns REFUSED if we do not serve the listed zone.
+ * Pass the message to the zone module for processing
+ * and returns the return status.
+ *
+ * Requires
+ *\li client to be valid.
+ */
diff --git a/lib/ns/include/ns/query.h b/lib/ns/include/ns/query.h
new file mode 100644
index 0000000..37e5567
--- /dev/null
+++ b/lib/ns/include/ns/query.h
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/buffer.h>
+#include <isc/netaddr.h>
+#include <isc/task.h>
+#include <isc/types.h>
+
+#include <dns/rdataset.h>
+#include <dns/resolver.h>
+#include <dns/rpz.h>
+#include <dns/types.h>
+
+#include <ns/types.h>
+
+/*% nameserver database version structure */
+typedef struct ns_dbversion {
+ dns_db_t *db;
+ dns_dbversion_t *version;
+ bool acl_checked;
+ bool queryok;
+ ISC_LINK(struct ns_dbversion) link;
+} ns_dbversion_t;
+
+/*%
+ * nameserver recursion parameters, to uniquely identify a recursion
+ * query; this is used to detect a recursion loop
+ */
+typedef struct ns_query_recparam {
+ dns_rdatatype_t qtype;
+ dns_name_t *qname;
+ dns_fixedname_t fqname;
+ dns_name_t *qdomain;
+ dns_fixedname_t fqdomain;
+} ns_query_recparam_t;
+
+/*% nameserver query structure */
+struct ns_query {
+ unsigned int attributes;
+ unsigned int restarts;
+ bool timerset;
+ dns_name_t *qname;
+ dns_name_t *origqname;
+ dns_rdatatype_t qtype;
+ unsigned int dboptions;
+ unsigned int fetchoptions;
+ dns_db_t *gluedb;
+ dns_db_t *authdb;
+ dns_zone_t *authzone;
+ bool authdbset;
+ bool isreferral;
+ isc_mutex_t fetchlock;
+ dns_fetch_t *fetch;
+ dns_fetch_t *prefetch;
+ ns_hookasync_t *hookactx;
+ dns_rpz_st_t *rpz_st;
+ isc_bufferlist_t namebufs;
+ ISC_LIST(ns_dbversion_t) activeversions;
+ ISC_LIST(ns_dbversion_t) freeversions;
+ dns_rdataset_t *dns64_aaaa;
+ dns_rdataset_t *dns64_sigaaaa;
+ bool *dns64_aaaaok;
+ unsigned int dns64_aaaaoklen;
+ unsigned int dns64_options;
+ unsigned int dns64_ttl;
+
+ struct {
+ dns_db_t *db;
+ dns_zone_t *zone;
+ dns_dbnode_t *node;
+ dns_rdatatype_t qtype;
+ dns_name_t *fname;
+ dns_fixedname_t fixed;
+ isc_result_t result;
+ dns_rdataset_t *rdataset;
+ dns_rdataset_t *sigrdataset;
+ bool authoritative;
+ bool is_zone;
+ } redirect;
+
+ ns_query_recparam_t recparam;
+
+ dns_keytag_t root_key_sentinel_keyid;
+ bool root_key_sentinel_is_ta;
+ bool root_key_sentinel_not_ta;
+};
+
+#define NS_QUERYATTR_RECURSIONOK 0x000001
+#define NS_QUERYATTR_CACHEOK 0x000002
+#define NS_QUERYATTR_PARTIALANSWER 0x000004
+#define NS_QUERYATTR_NAMEBUFUSED 0x000008
+#define NS_QUERYATTR_RECURSING 0x000010
+#define NS_QUERYATTR_QUERYOKVALID 0x000040
+#define NS_QUERYATTR_QUERYOK 0x000080
+#define NS_QUERYATTR_WANTRECURSION 0x000100
+#define NS_QUERYATTR_SECURE 0x000200
+#define NS_QUERYATTR_NOAUTHORITY 0x000400
+#define NS_QUERYATTR_NOADDITIONAL 0x000800
+#define NS_QUERYATTR_CACHEACLOKVALID 0x001000
+#define NS_QUERYATTR_CACHEACLOK 0x002000
+#define NS_QUERYATTR_DNS64 0x004000
+#define NS_QUERYATTR_DNS64EXCLUDE 0x008000
+#define NS_QUERYATTR_RRL_CHECKED 0x010000
+#define NS_QUERYATTR_REDIRECT 0x020000
+#define NS_QUERYATTR_ANSWERED 0x040000
+#define NS_QUERYATTR_STALEOK 0x080000
+#define NS_QUERYATTR_STALEPENDING 0x100000
+
+typedef struct query_ctx query_ctx_t;
+
+/* query context structure */
+struct query_ctx {
+ isc_buffer_t *dbuf; /* name buffer */
+ dns_name_t *fname; /* found name from DB lookup */
+ dns_name_t *tname; /* temporary name, used
+ * when processing ANY
+ * queries */
+ dns_rdataset_t *rdataset; /* found rdataset */
+ dns_rdataset_t *sigrdataset; /* found sigrdataset */
+ dns_rdataset_t *noqname; /* rdataset needing
+ * NOQNAME proof */
+ dns_rdatatype_t qtype;
+ dns_rdatatype_t type;
+
+ unsigned int options; /* DB lookup options */
+
+ bool redirected; /* nxdomain redirected? */
+ bool is_zone; /* is DB a zone DB? */
+ bool is_staticstub_zone;
+ bool resuming; /* resumed from recursion? */
+ bool dns64, dns64_exclude, rpz;
+ bool authoritative; /* authoritative query? */
+ bool want_restart; /* CNAME chain or other
+ * restart needed */
+ bool refresh_rrset; /* stale RRset refresh needed */
+ bool need_wildcardproof; /* wildcard proof needed */
+ bool nxrewrite; /* negative answer from RPZ */
+ bool findcoveringnsec; /* lookup covering NSEC */
+ bool answer_has_ns; /* NS is in answer */
+ dns_fixedname_t wildcardname; /* name needing wcard proof */
+ dns_fixedname_t dsname; /* name needing DS */
+
+ ns_client_t *client; /* client object */
+ bool detach_client; /* client needs detaching */
+
+ dns_fetchevent_t *event; /* recursion event */
+
+ dns_db_t *db; /* zone or cache database */
+ dns_dbversion_t *version; /* DB version */
+ dns_dbnode_t *node; /* DB node */
+
+ dns_db_t *zdb; /* zone DB values, saved */
+ dns_dbnode_t *znode; /* while searching cache */
+ dns_name_t *zfname; /* for a better answer */
+ dns_dbversion_t *zversion;
+ dns_rdataset_t *zrdataset;
+ dns_rdataset_t *zsigrdataset;
+
+ dns_rpz_st_t *rpz_st; /* RPZ state */
+ dns_zone_t *zone; /* zone to search */
+
+ dns_view_t *view; /* client view */
+
+ isc_result_t result; /* query result */
+ int line; /* line to report error */
+};
+
+typedef isc_result_t (*ns_query_starthookasync_t)(
+ query_ctx_t *qctx, isc_mem_t *mctx, void *arg, isc_task_t *task,
+ isc_taskaction_t action, void *evarg, ns_hookasync_t **ctxp);
+
+/*
+ * The following functions are expected to be used only within query.c
+ * and query modules.
+ */
+
+isc_result_t
+ns_query_done(query_ctx_t *qctx);
+/*%<
+ * Finalize this phase of the query process:
+ *
+ * - Clean up.
+ * - If we have an answer ready (positive or negative), send it.
+ * - If we need to restart for a chaining query, call ns__query_start() again.
+ * - If we've started recursion, then just clean up; things will be
+ * restarted via fetch_callback()/query_resume().
+ */
+
+isc_result_t
+ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
+ dns_name_t *qdomain, dns_rdataset_t *nameservers,
+ bool resuming);
+/*%<
+ * Prepare client for recursion, then create a resolver fetch, with
+ * the event callback set to fetch_callback(). Afterward we terminate
+ * this phase of the query, and resume with a new query context when
+ * recursion completes.
+ */
+
+isc_result_t
+ns_query_hookasync(query_ctx_t *qctx, ns_query_starthookasync_t runasync,
+ void *arg);
+/*%<
+ * Prepare the client for an asynchronous hook action, then call the
+ * specified 'runasync' function to start an asynchronous process running
+ * in the background. This function works similarly to ns_query_recurse(),
+ * but is expected to be called from a query hook action to support
+ * asynchronous event handling in a hook. A typical use case would be for
+ * a plugin to initiate recursion, but it may also be used to carry out
+ * other time-consuming tasks without blocking the caller or the worker
+ * thread.
+ *
+ * The calling plugin action must pass 'qctx' as passed from the query
+ * module.
+ *
+ * Once a plugin action calls this function, the ownership of 'qctx' is
+ * essentially transferred to the query module. Regardless of the return
+ * value of this function, the hook must not use 'qctx' anymore.
+ *
+ * This function must not be called after ns_query_recurse() is called,
+ * until the fetch is completed, as it needs resources that
+ * ns_query_recurse() would also use.
+ *
+ * See hooks.h for details about how 'runasync' is supposed to work, and
+ * other aspects of hook-triggered asynchronous event handling.
+ */
+
+isc_result_t
+ns_query_init(ns_client_t *client);
+
+void
+ns_query_free(ns_client_t *client);
+
+void
+ns_query_start(ns_client_t *client, isc_nmhandle_t *handle);
+
+void
+ns_query_cancel(ns_client_t *client);
+
+/*
+ * The following functions are expected to be used only within query.c
+ * and query modules.
+ */
+
+isc_result_t
+ns__query_sfcache(query_ctx_t *qctx);
+/*%<
+ * (Must not be used outside this module and its associated unit tests.)
+ */
+
+isc_result_t
+ns__query_start(query_ctx_t *qctx);
+/*%<
+ * (Must not be used outside this module and its associated unit tests.)
+ */
diff --git a/lib/ns/include/ns/server.h b/lib/ns/include/ns/server.h
new file mode 100644
index 0000000..206c495
--- /dev/null
+++ b/lib/ns/include/ns/server.h
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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 <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/fuzz.h>
+#include <isc/log.h>
+#include <isc/magic.h>
+#include <isc/quota.h>
+#include <isc/random.h>
+#include <isc/sockaddr.h>
+#include <isc/types.h>
+
+#include <dns/acl.h>
+#include <dns/types.h>
+
+#include <ns/events.h>
+#include <ns/types.h>
+
+#define NS_SERVER_LOGQUERIES 0x00000001U /*%< log queries */
+#define NS_SERVER_NOAA 0x00000002U /*%< -T noaa */
+#define NS_SERVER_NOSOA 0x00000004U /*%< -T nosoa */
+#define NS_SERVER_NONEAREST 0x00000008U /*%< -T nonearest */
+#define NS_SERVER_NOEDNS 0x00000020U /*%< -T noedns */
+#define NS_SERVER_DROPEDNS 0x00000040U /*%< -T dropedns */
+#define NS_SERVER_NOTCP 0x00000080U /*%< -T notcp */
+#define NS_SERVER_DISABLE4 0x00000100U /*%< -6 */
+#define NS_SERVER_DISABLE6 0x00000200U /*%< -4 */
+#define NS_SERVER_FIXEDLOCAL 0x00000400U /*%< -T fixedlocal */
+#define NS_SERVER_SIGVALINSECS 0x00000800U /*%< -T sigvalinsecs */
+#define NS_SERVER_EDNSFORMERR 0x00001000U /*%< -T ednsformerr (STD13) */
+#define NS_SERVER_EDNSNOTIMP 0x00002000U /*%< -T ednsnotimp */
+#define NS_SERVER_EDNSREFUSED 0x00004000U /*%< -T ednsrefused */
+#define NS_SERVER_TRANSFERINSECS 0x00008000U /*%< -T transferinsecs */
+#define NS_SERVER_TRANSFERSLOWLY 0x00010000U /*%< -T transferslowly */
+#define NS_SERVER_TRANSFERSTUCK 0x00020000U /*%< -T transferstuck */
+
+/*%
+ * Type for callback function to get hostname.
+ */
+typedef isc_result_t (*ns_hostnamecb_t)(char *buf, size_t len);
+
+/*%
+ * Type for callback function to signal the fuzzer thread
+ * when built with AFL.
+ */
+typedef void (*ns_fuzzcb_t)(void);
+
+/*%
+ * Type for callback function to get the view that can answer a query.
+ */
+typedef isc_result_t (*ns_matchview_t)(
+ isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, dns_message_t *message,
+ dns_aclenv_t *env, isc_result_t *sigresultp, dns_view_t **viewp);
+
+/*%
+ * Server context.
+ */
+struct ns_server {
+ unsigned int magic;
+ isc_mem_t *mctx;
+
+ isc_refcount_t references;
+
+ /*% Server cookie secret and algorithm */
+ unsigned char secret[32];
+ ns_cookiealg_t cookiealg;
+ ns_altsecretlist_t altsecrets;
+ bool answercookie;
+
+ /*% Quotas */
+ isc_quota_t recursionquota;
+ isc_quota_t tcpquota;
+ isc_quota_t xfroutquota;
+ isc_quota_t updquota;
+ ISC_LIST(isc_quota_t) http_quotas;
+ isc_mutex_t http_quotas_lock;
+
+ /*% Test options and other configurables */
+ uint32_t options;
+
+ dns_acl_t *blackholeacl;
+ dns_acl_t *keepresporder;
+ uint16_t udpsize;
+ uint16_t transfer_tcp_message_size;
+ bool interface_auto;
+ dns_tkeyctx_t *tkeyctx;
+
+ /*% Server id for NSID */
+ char *server_id;
+ bool usehostname;
+
+ /*% Fuzzer callback */
+ isc_fuzztype_t fuzztype;
+ ns_fuzzcb_t fuzznotify;
+
+ /*% Callback to find a matching view for a query */
+ ns_matchview_t matchingview;
+
+ /*% Stats counters */
+ ns_stats_t *nsstats;
+ dns_stats_t *rcvquerystats;
+ dns_stats_t *opcodestats;
+ dns_stats_t *rcodestats;
+
+ isc_stats_t *udpinstats4;
+ isc_stats_t *udpoutstats4;
+ isc_stats_t *udpinstats6;
+ isc_stats_t *udpoutstats6;
+
+ isc_stats_t *tcpinstats4;
+ isc_stats_t *tcpoutstats4;
+ isc_stats_t *tcpinstats6;
+ isc_stats_t *tcpoutstats6;
+};
+
+struct ns_altsecret {
+ ISC_LINK(ns_altsecret_t) link;
+ unsigned char secret[32];
+};
+
+isc_result_t
+ns_server_create(isc_mem_t *mctx, ns_matchview_t matchingview,
+ ns_server_t **sctxp);
+/*%<
+ * Create a server context object with default settings.
+ */
+
+void
+ns_server_attach(ns_server_t *src, ns_server_t **dest);
+/*%<
+ * Attach a server context.
+ *
+ * Requires:
+ *\li 'src' is valid.
+ */
+
+void
+ns_server_detach(ns_server_t **sctxp);
+/*%<
+ * Detach from a server context. If its reference count drops to zero, destroy
+ * it, freeing its memory.
+ *
+ * Requires:
+ *\li '*sctxp' is valid.
+ * Ensures:
+ *\li '*sctxp' is NULL on return.
+ */
+
+isc_result_t
+ns_server_setserverid(ns_server_t *sctx, const char *serverid);
+/*%<
+ * Set sctx->server_id to 'serverid'. If it was set previously, free the memory.
+ *
+ * Requires:
+ *\li 'sctx' is valid.
+ */
+
+void
+ns_server_setoption(ns_server_t *sctx, unsigned int option, bool value);
+/*%<
+ * Set the given options on (if 'value' == #true)
+ * or off (if 'value' == #false).
+ *
+ * Requires:
+ *\li 'sctx' is valid
+ */
+
+bool
+ns_server_getoption(ns_server_t *sctx, unsigned int option);
+/*%<
+ * Returns the current value of the specified server option.
+ *
+ * Requires:
+ *\li 'sctx' is valid.
+ */
+
+void
+ns_server_append_http_quota(ns_server_t *sctx, isc_quota_t *http_quota);
+/*%<
+ * Add a quota to the list of HTTP quotas to destroy it safely later.
+ *
+ * Requires:
+ *\li 'sctx' is valid;
+ *\li 'http_quota' is not 'NULL'.
+ */
diff --git a/lib/ns/include/ns/sortlist.h b/lib/ns/include/ns/sortlist.h
new file mode 100644
index 0000000..3333e9c
--- /dev/null
+++ b/lib/ns/include/ns/sortlist.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+#pragma once
+
+/*! \file */
+
+#include <isc/types.h>
+
+#include <dns/acl.h>
+#include <dns/types.h>
+
+/*%
+ * Type for callback functions that rank addresses.
+ */
+typedef int (*dns_addressorderfunc_t)(const isc_netaddr_t *address,
+ const void *arg);
+
+/*%
+ * Return value type for setup_sortlist.
+ */
+typedef enum {
+ NS_SORTLISTTYPE_NONE,
+ NS_SORTLISTTYPE_1ELEMENT,
+ NS_SORTLISTTYPE_2ELEMENT
+} ns_sortlisttype_t;
+
+ns_sortlisttype_t
+ns_sortlist_setup(dns_acl_t *acl, dns_aclenv_t *env, isc_netaddr_t *clientaddr,
+ void **argp);
+/*%<
+ * Find the sortlist statement in 'acl' (for ACL environment 'env')
+ * that applies to 'clientaddr', if any.
+ *
+ * If a 1-element sortlist item applies, return NS_SORTLISTTYPE_1ELEMENT and
+ * make '*argp' point to the matching subelement.
+ *
+ * If a 2-element sortlist item applies, return NS_SORTLISTTYPE_2ELEMENT and
+ * make '*argp' point to ACL that forms the second element.
+ *
+ * If no sortlist item applies, return NS_SORTLISTTYPE_NONE and set '*argp'
+ * to NULL.
+ */
+
+int
+ns_sortlist_addrorder1(const isc_netaddr_t *addr, const void *arg);
+/*%<
+ * Find the sort order of 'addr' in 'arg', the matching element
+ * of a 1-element top-level sortlist statement.
+ */
+
+int
+ns_sortlist_addrorder2(const isc_netaddr_t *addr, const void *arg);
+/*%<
+ * Find the sort order of 'addr' in 'arg', a topology-like
+ * ACL forming the second element in a 2-element top-level
+ * sortlist statement.
+ */
+
+void
+ns_sortlist_byaddrsetup(dns_acl_t *sortlist_acl, dns_aclenv_t *env,
+ isc_netaddr_t *client_addr,
+ dns_addressorderfunc_t *orderp, void **argp);
+/*%<
+ * Find the sortlist statement in 'acl' that applies to 'clientaddr', if any.
+ * If a sortlist statement applies, return in '*orderp' a pointer to a function
+ * for ranking network addresses based on that sortlist statement, and in
+ * '*argp' an argument to pass to said function. If no sortlist statement
+ * applies, set '*orderp' and '*argp' to NULL.
+ */
diff --git a/lib/ns/include/ns/stats.h b/lib/ns/include/ns/stats.h
new file mode 100644
index 0000000..c235384
--- /dev/null
+++ b/lib/ns/include/ns/stats.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain 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/ns/stats.h */
+
+#include <ns/types.h>
+
+/*%
+ * Server statistics counters. Used as isc_statscounter_t values.
+ */
+enum {
+ ns_statscounter_requestv4 = 0,
+ ns_statscounter_requestv6 = 1,
+ ns_statscounter_edns0in = 2,
+ ns_statscounter_badednsver = 3,
+ ns_statscounter_tsigin = 4,
+ ns_statscounter_sig0in = 5,
+ ns_statscounter_invalidsig = 6,
+ ns_statscounter_requesttcp = 7,
+
+ ns_statscounter_authrej = 8,
+ ns_statscounter_recurserej = 9,
+ ns_statscounter_xfrrej = 10,
+ ns_statscounter_updaterej = 11,
+
+ ns_statscounter_response = 12,
+ ns_statscounter_truncatedresp = 13,
+ ns_statscounter_edns0out = 14,
+ ns_statscounter_tsigout = 15,
+ ns_statscounter_sig0out = 16,
+
+ ns_statscounter_success = 17,
+ ns_statscounter_authans = 18,
+ ns_statscounter_nonauthans = 19,
+ ns_statscounter_referral = 20,
+ ns_statscounter_nxrrset = 21,
+ ns_statscounter_servfail = 22,
+ ns_statscounter_formerr = 23,
+ ns_statscounter_nxdomain = 24,
+ ns_statscounter_recursion = 25,
+ ns_statscounter_duplicate = 26,
+ ns_statscounter_dropped = 27,
+ ns_statscounter_failure = 28,
+
+ ns_statscounter_xfrdone = 29,
+
+ ns_statscounter_updatereqfwd = 30,
+ ns_statscounter_updaterespfwd = 31,
+ ns_statscounter_updatefwdfail = 32,
+ ns_statscounter_updatedone = 33,
+ ns_statscounter_updatefail = 34,
+ ns_statscounter_updatebadprereq = 35,
+
+ ns_statscounter_recursclients = 36,
+
+ ns_statscounter_dns64 = 37,
+
+ ns_statscounter_ratedropped = 38,
+ ns_statscounter_rateslipped = 39,
+
+ ns_statscounter_rpz_rewrites = 40,
+
+ ns_statscounter_udp = 41,
+ ns_statscounter_tcp = 42,
+
+ ns_statscounter_nsidopt = 43,
+ ns_statscounter_expireopt = 44,
+ ns_statscounter_otheropt = 45,
+ ns_statscounter_ecsopt = 46,
+ ns_statscounter_padopt = 47,
+ ns_statscounter_keepaliveopt = 48,
+
+ ns_statscounter_nxdomainredirect = 49,
+ ns_statscounter_nxdomainredirect_rlookup = 50,
+
+ ns_statscounter_cookiein = 51,
+ ns_statscounter_cookiebadsize = 52,
+ ns_statscounter_cookiebadtime = 53,
+ ns_statscounter_cookienomatch = 54,
+ ns_statscounter_cookiematch = 55,
+ ns_statscounter_cookienew = 56,
+ ns_statscounter_badcookie = 57,
+
+ ns_statscounter_nxdomainsynth = 58,
+ ns_statscounter_nodatasynth = 59,
+ ns_statscounter_wildcardsynth = 60,
+
+ ns_statscounter_trystale = 61,
+ ns_statscounter_usedstale = 62,
+
+ ns_statscounter_prefetch = 63,
+ ns_statscounter_keytagopt = 64,
+
+ ns_statscounter_tcphighwater = 65,
+
+ ns_statscounter_reclimitdropped = 66,
+
+ ns_statscounter_updatequota = 67,
+
+ ns_statscounter_max = 68,
+};
+
+void
+ns_stats_attach(ns_stats_t *stats, ns_stats_t **statsp);
+
+void
+ns_stats_detach(ns_stats_t **statsp);
+
+isc_result_t
+ns_stats_create(isc_mem_t *mctx, int ncounters, ns_stats_t **statsp);
+
+void
+ns_stats_increment(ns_stats_t *stats, isc_statscounter_t counter);
+
+void
+ns_stats_decrement(ns_stats_t *stats, isc_statscounter_t counter);
+
+isc_stats_t *
+ns_stats_get(ns_stats_t *stats);
+
+void
+ns_stats_update_if_greater(ns_stats_t *stats, isc_statscounter_t counter,
+ isc_statscounter_t value);
+
+isc_statscounter_t
+ns_stats_get_counter(ns_stats_t *stats, isc_statscounter_t counter);
diff --git a/lib/ns/include/ns/types.h b/lib/ns/include/ns/types.h
new file mode 100644
index 0000000..85205d7
--- /dev/null
+++ b/lib/ns/include/ns/types.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
+
+/*! \file */
+
+typedef struct ns_altsecret ns_altsecret_t;
+typedef ISC_LIST(ns_altsecret_t) ns_altsecretlist_t;
+typedef struct ns_client ns_client_t;
+typedef struct ns_clientmgr ns_clientmgr_t;
+typedef struct ns_plugin ns_plugin_t;
+typedef ISC_LIST(ns_plugin_t) ns_plugins_t;
+typedef struct ns_interface ns_interface_t;
+typedef struct ns_interfacemgr ns_interfacemgr_t;
+typedef struct ns_query ns_query_t;
+typedef struct ns_server ns_server_t;
+typedef struct ns_stats ns_stats_t;
+typedef struct ns_hookasync ns_hookasync_t;
+
+typedef enum { ns_cookiealg_aes, ns_cookiealg_siphash24 } ns_cookiealg_t;
+
+#define NS_COOKIE_VERSION_1 1
diff --git a/lib/ns/include/ns/update.h b/lib/ns/include/ns/update.h
new file mode 100644
index 0000000..fbabdf9
--- /dev/null
+++ b/lib/ns/include/ns/update.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
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * RFC2136 Dynamic Update
+ */
+
+/***
+ *** Imports
+ ***/
+
+#include <isc/result.h>
+
+#include <dns/types.h>
+
+/***
+ *** Types.
+ ***/
+
+/***
+ *** Functions
+ ***/
+
+void
+ns_update_start(ns_client_t *client, isc_nmhandle_t *handle,
+ isc_result_t sigresult);
diff --git a/lib/ns/include/ns/xfrout.h b/lib/ns/include/ns/xfrout.h
new file mode 100644
index 0000000..d36060e
--- /dev/null
+++ b/lib/ns/include/ns/xfrout.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
+
+/*****
+***** Module Info
+*****/
+
+/*! \file
+ * \brief
+ * Outgoing zone transfers (AXFR + IXFR).
+ */
+
+/***
+ *** Functions
+ ***/
+
+void
+ns_xfr_start(ns_client_t *client, dns_rdatatype_t xfrtype);
diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c
new file mode 100644
index 0000000..50b87df
--- /dev/null
+++ b/lib/ns/interfacemgr.c
@@ -0,0 +1,1438 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/interfaceiter.h>
+#include <isc/netmgr.h>
+#include <isc/os.h>
+#include <isc/random.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/dispatch.h>
+
+#include <ns/client.h>
+#include <ns/interfacemgr.h>
+#include <ns/log.h>
+#include <ns/server.h>
+#include <ns/stats.h>
+
+#ifdef HAVE_NET_ROUTE_H
+#include <net/route.h>
+#if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define MSGHDR rt_msghdr
+#define MSGTYPE rtm_type
+#endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \
+ * defined(RTM_DELADDR) */
+#endif /* ifdef HAVE_NET_ROUTE_H */
+
+#if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H)
+#define LINUX_NETLINK_AVAILABLE
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#if defined(RTM_NEWADDR) && defined(RTM_DELADDR)
+#define MSGHDR nlmsghdr
+#define MSGTYPE nlmsg_type
+#endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */
+#endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \
+ */
+
+#define LISTENING(ifp) (((ifp)->flags & NS_INTERFACEFLAG_LISTENING) != 0)
+
+#ifdef TUNE_LARGE
+#define UDPBUFFERS 32768
+#else /* ifdef TUNE_LARGE */
+#define UDPBUFFERS 1000
+#endif /* TUNE_LARGE */
+
+#define IFMGR_MAGIC ISC_MAGIC('I', 'F', 'M', 'G')
+#define NS_INTERFACEMGR_VALID(t) ISC_MAGIC_VALID(t, IFMGR_MAGIC)
+
+#define IFMGR_COMMON_LOGARGS \
+ ns_lctx, NS_LOGCATEGORY_NETWORK, NS_LOGMODULE_INTERFACEMGR
+
+/*% nameserver interface manager structure */
+struct ns_interfacemgr {
+ unsigned int magic; /*%< Magic number */
+ isc_refcount_t references;
+ isc_mutex_t lock;
+ isc_mem_t *mctx; /*%< Memory context */
+ ns_server_t *sctx; /*%< Server context */
+ isc_taskmgr_t *taskmgr; /*%< Task manager */
+ isc_task_t *task; /*%< Task */
+ isc_timermgr_t *timermgr; /*%< Timer manager */
+ isc_nm_t *nm; /*%< Net manager */
+ int ncpus; /*%< Number of workers */
+ dns_dispatchmgr_t *dispatchmgr;
+ unsigned int generation; /*%< Current generation no */
+ ns_listenlist_t *listenon4;
+ ns_listenlist_t *listenon6;
+ dns_aclenv_t *aclenv; /*%< Localhost/localnets ACLs */
+ ISC_LIST(ns_interface_t) interfaces; /*%< List of interfaces */
+ ISC_LIST(isc_sockaddr_t) listenon;
+ int backlog; /*%< Listen queue size */
+ atomic_bool shuttingdown; /*%< Interfacemgr shutting down */
+ ns_clientmgr_t **clientmgrs; /*%< Client managers */
+ isc_nmhandle_t *route;
+};
+
+static void
+purge_old_interfaces(ns_interfacemgr_t *mgr);
+
+static void
+clearlistenon(ns_interfacemgr_t *mgr);
+
+static bool
+need_rescan(ns_interfacemgr_t *mgr, struct MSGHDR *rtm, size_t len) {
+ if (rtm->MSGTYPE != RTM_NEWADDR && rtm->MSGTYPE != RTM_DELADDR) {
+ return (false);
+ }
+
+#ifndef LINUX_NETLINK_AVAILABLE
+ UNUSED(mgr);
+ UNUSED(len);
+ /* On most systems, any NEWADDR or DELADDR means we rescan */
+ return (true);
+#else /* LINUX_NETLINK_AVAILABLE */
+ /* ...but on linux we need to check the messages more carefully */
+ for (struct MSGHDR *nlh = rtm;
+ NLMSG_OK(nlh, len) && nlh->nlmsg_type != NLMSG_DONE;
+ nlh = NLMSG_NEXT(nlh, len))
+ {
+ struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
+ struct rtattr *rth = IFA_RTA(ifa);
+ size_t rtl = IFA_PAYLOAD(nlh);
+
+ while (rtl > 0 && RTA_OK(rth, rtl)) {
+ /*
+ * Look for IFA_ADDRESS to detect IPv6 interface
+ * state changes.
+ */
+ if (rth->rta_type == IFA_ADDRESS &&
+ ifa->ifa_family == AF_INET6)
+ {
+ bool existed = false;
+ bool was_listening = false;
+ isc_netaddr_t addr = { 0 };
+ ns_interface_t *ifp = NULL;
+
+ isc_netaddr_fromin6(&addr, RTA_DATA(rth));
+ INSIST(isc_netaddr_getzone(&addr) == 0);
+
+ /*
+ * Check whether we were listening on the
+ * address. We need to do this as the
+ * Linux kernel seems to issue messages
+ * containing IFA_ADDRESS far more often
+ * than the actual state changes (on
+ * router advertisements?)
+ */
+ LOCK(&mgr->lock);
+ for (ifp = ISC_LIST_HEAD(mgr->interfaces);
+ ifp != NULL;
+ ifp = ISC_LIST_NEXT(ifp, link))
+ {
+ isc_netaddr_t tmp = { 0 };
+ isc_netaddr_fromsockaddr(&tmp,
+ &ifp->addr);
+ if (tmp.family != AF_INET6) {
+ continue;
+ }
+
+ /*
+ * We have to nullify the zone (IPv6
+ * scope ID) because we haven't got one
+ * from the kernel. Otherwise match
+ * could fail even for an existing
+ * address.
+ */
+ isc_netaddr_setzone(&tmp, 0);
+ if (isc_netaddr_equal(&tmp, &addr)) {
+ was_listening = LISTENING(ifp);
+ existed = true;
+ break;
+ }
+ }
+ UNLOCK(&mgr->lock);
+
+ /*
+ * Do rescan if the state of the interface
+ * has changed.
+ */
+ if ((!existed && rtm->MSGTYPE == RTM_NEWADDR) ||
+ (existed && was_listening &&
+ rtm->MSGTYPE == RTM_DELADDR))
+ {
+ return (true);
+ }
+ } else if (rth->rta_type == IFA_ADDRESS &&
+ ifa->ifa_family == AF_INET)
+ {
+ /*
+ * It seems that the IPv4 P2P link state
+ * has changed.
+ */
+ return (true);
+ } else if (rth->rta_type == IFA_LOCAL) {
+ /*
+ * Local address state has changed - do
+ * rescan.
+ */
+ return (true);
+ }
+ rth = RTA_NEXT(rth, rtl);
+ }
+ }
+#endif /* LINUX_NETLINK_AVAILABLE */
+
+ return (false);
+}
+
+static void
+route_recv(isc_nmhandle_t *handle, isc_result_t eresult, isc_region_t *region,
+ void *arg) {
+ ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg;
+ struct MSGHDR *rtm = NULL;
+ size_t rtmlen;
+
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9), "route_recv: %s",
+ isc_result_totext(eresult));
+
+ if (handle == NULL) {
+ return;
+ }
+
+ if (eresult != ISC_R_SUCCESS) {
+ if (eresult != ISC_R_CANCELED && eresult != ISC_R_SHUTTINGDOWN)
+ {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "automatic interface scanning "
+ "terminated: %s",
+ isc_result_totext(eresult));
+ }
+ isc_nmhandle_detach(&mgr->route);
+ ns_interfacemgr_detach(&mgr);
+ return;
+ }
+
+ rtm = (struct MSGHDR *)region->base;
+ rtmlen = region->length;
+
+#ifdef RTM_VERSION
+ if (rtm->rtm_version != RTM_VERSION) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "automatic interface rescanning disabled: "
+ "rtm->rtm_version mismatch (%u != %u) "
+ "recompile required",
+ rtm->rtm_version, RTM_VERSION);
+ isc_nmhandle_detach(&mgr->route);
+ ns_interfacemgr_detach(&mgr);
+ return;
+ }
+#endif /* ifdef RTM_VERSION */
+
+ REQUIRE(mgr->route != NULL);
+
+ if (need_rescan(mgr, rtm, rtmlen) && mgr->sctx->interface_auto) {
+ ns_interfacemgr_scan(mgr, false, false);
+ }
+
+ isc_nm_read(handle, route_recv, mgr);
+ return;
+}
+
+static void
+route_connected(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
+ ns_interfacemgr_t *mgr = (ns_interfacemgr_t *)arg;
+
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_DEBUG(9),
+ "route_connected: %s", isc_result_totext(eresult));
+
+ if (eresult != ISC_R_SUCCESS) {
+ ns_interfacemgr_detach(&mgr);
+ return;
+ }
+
+ INSIST(mgr->route == NULL);
+
+ isc_nmhandle_attach(handle, &mgr->route);
+ isc_nm_read(handle, route_recv, mgr);
+}
+
+isc_result_t
+ns_interfacemgr_create(isc_mem_t *mctx, ns_server_t *sctx,
+ isc_taskmgr_t *taskmgr, isc_timermgr_t *timermgr,
+ isc_nm_t *nm, dns_dispatchmgr_t *dispatchmgr,
+ isc_task_t *task, dns_geoip_databases_t *geoip,
+ int ncpus, bool scan, ns_interfacemgr_t **mgrp) {
+ isc_result_t result;
+ ns_interfacemgr_t *mgr = NULL;
+
+ UNUSED(task);
+
+ REQUIRE(mctx != NULL);
+ REQUIRE(mgrp != NULL);
+ REQUIRE(*mgrp == NULL);
+
+ mgr = isc_mem_get(mctx, sizeof(*mgr));
+ *mgr = (ns_interfacemgr_t){ .taskmgr = taskmgr,
+ .timermgr = timermgr,
+ .nm = nm,
+ .dispatchmgr = dispatchmgr,
+ .generation = 1,
+ .ncpus = ncpus };
+
+ isc_mem_attach(mctx, &mgr->mctx);
+ ns_server_attach(sctx, &mgr->sctx);
+
+ isc_mutex_init(&mgr->lock);
+
+ result = isc_task_create_bound(taskmgr, 0, &mgr->task, 0);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_lock;
+ }
+
+ atomic_init(&mgr->shuttingdown, false);
+
+ ISC_LIST_INIT(mgr->interfaces);
+ ISC_LIST_INIT(mgr->listenon);
+
+ /*
+ * The listen-on lists are initially empty.
+ */
+ result = ns_listenlist_create(mctx, &mgr->listenon4);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_task;
+ }
+ ns_listenlist_attach(mgr->listenon4, &mgr->listenon6);
+
+ result = dns_aclenv_create(mctx, &mgr->aclenv);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_listenon;
+ }
+#if defined(HAVE_GEOIP2)
+ mgr->aclenv->geoip = geoip;
+#else /* if defined(HAVE_GEOIP2) */
+ UNUSED(geoip);
+#endif /* if defined(HAVE_GEOIP2) */
+
+ isc_refcount_init(&mgr->references, 1);
+ mgr->magic = IFMGR_MAGIC;
+ *mgrp = mgr;
+
+ mgr->clientmgrs = isc_mem_get(mgr->mctx,
+ mgr->ncpus * sizeof(mgr->clientmgrs[0]));
+ for (size_t i = 0; i < (size_t)mgr->ncpus; i++) {
+ result = ns_clientmgr_create(mgr->sctx, mgr->taskmgr,
+ mgr->timermgr, mgr->aclenv, (int)i,
+ &mgr->clientmgrs[i]);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ }
+
+ if (scan) {
+ ns_interfacemgr_t *imgr = NULL;
+
+ ns_interfacemgr_attach(mgr, &imgr);
+
+ result = isc_nm_routeconnect(nm, route_connected, imgr, 0);
+ if (result == ISC_R_NOTIMPLEMENTED) {
+ ns_interfacemgr_detach(&imgr);
+ }
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
+ "unable to open route socket: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+
+cleanup_listenon:
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+cleanup_task:
+ isc_task_detach(&mgr->task);
+cleanup_lock:
+ isc_mutex_destroy(&mgr->lock);
+ ns_server_detach(&mgr->sctx);
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr));
+ return (result);
+}
+
+static void
+ns_interfacemgr_destroy(ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ isc_refcount_destroy(&mgr->references);
+
+ dns_aclenv_detach(&mgr->aclenv);
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_detach(&mgr->listenon6);
+ clearlistenon(mgr);
+ isc_mutex_destroy(&mgr->lock);
+ for (size_t i = 0; i < (size_t)mgr->ncpus; i++) {
+ ns_clientmgr_detach(&mgr->clientmgrs[i]);
+ }
+ isc_mem_put(mgr->mctx, mgr->clientmgrs,
+ mgr->ncpus * sizeof(mgr->clientmgrs[0]));
+
+ if (mgr->sctx != NULL) {
+ ns_server_detach(&mgr->sctx);
+ }
+ isc_task_detach(&mgr->task);
+ mgr->magic = 0;
+ isc_mem_putanddetach(&mgr->mctx, mgr, sizeof(*mgr));
+}
+
+void
+ns_interfacemgr_setbacklog(ns_interfacemgr_t *mgr, int backlog) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ LOCK(&mgr->lock);
+ mgr->backlog = backlog;
+ UNLOCK(&mgr->lock);
+}
+
+dns_aclenv_t *
+ns_interfacemgr_getaclenv(ns_interfacemgr_t *mgr) {
+ dns_aclenv_t *aclenv = NULL;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ LOCK(&mgr->lock);
+ aclenv = mgr->aclenv;
+ UNLOCK(&mgr->lock);
+
+ return (aclenv);
+}
+
+void
+ns_interfacemgr_attach(ns_interfacemgr_t *source, ns_interfacemgr_t **target) {
+ REQUIRE(NS_INTERFACEMGR_VALID(source));
+ isc_refcount_increment(&source->references);
+ *target = source;
+}
+
+void
+ns_interfacemgr_detach(ns_interfacemgr_t **targetp) {
+ ns_interfacemgr_t *target = *targetp;
+ *targetp = NULL;
+ REQUIRE(target != NULL);
+ REQUIRE(NS_INTERFACEMGR_VALID(target));
+ if (isc_refcount_decrement(&target->references) == 1) {
+ ns_interfacemgr_destroy(target);
+ }
+}
+
+void
+ns_interfacemgr_shutdown(ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ /*%
+ * Shut down and detach all interfaces.
+ * By incrementing the generation count, we make
+ * purge_old_interfaces() consider all interfaces "old".
+ */
+ mgr->generation++;
+ atomic_store(&mgr->shuttingdown, true);
+
+ purge_old_interfaces(mgr);
+
+ if (mgr->route != NULL) {
+ isc_nm_cancelread(mgr->route);
+ }
+
+ for (size_t i = 0; i < (size_t)mgr->ncpus; i++) {
+ ns_clientmgr_shutdown(mgr->clientmgrs[i]);
+ }
+}
+
+static void
+interface_create(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name,
+ ns_interface_t **ifpret) {
+ ns_interface_t *ifp = NULL;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ ifp = isc_mem_get(mgr->mctx, sizeof(*ifp));
+ *ifp = (ns_interface_t){ .generation = mgr->generation, .addr = *addr };
+
+ strlcpy(ifp->name, name, sizeof(ifp->name));
+
+ isc_mutex_init(&ifp->lock);
+
+ isc_refcount_init(&ifp->ntcpaccepting, 0);
+ isc_refcount_init(&ifp->ntcpactive, 0);
+
+ ISC_LINK_INIT(ifp, link);
+
+ ns_interfacemgr_attach(mgr, &ifp->mgr);
+ ifp->magic = IFACE_MAGIC;
+
+ LOCK(&mgr->lock);
+ ISC_LIST_APPEND(mgr->interfaces, ifp, link);
+ UNLOCK(&mgr->lock);
+
+ *ifpret = ifp;
+}
+
+static isc_result_t
+ns_interface_listenudp(ns_interface_t *ifp) {
+ isc_result_t result;
+
+ /* Reserve space for an ns_client_t with the netmgr handle */
+ result = isc_nm_listenudp(ifp->mgr->nm, &ifp->addr, ns__client_request,
+ ifp, sizeof(ns_client_t),
+ &ifp->udplistensocket);
+ return (result);
+}
+
+static isc_result_t
+ns_interface_listentcp(ns_interface_t *ifp) {
+ isc_result_t result;
+
+ result = isc_nm_listentcpdns(
+ ifp->mgr->nm, &ifp->addr, ns__client_request, ifp,
+ ns__client_tcpconn, ifp, sizeof(ns_client_t), ifp->mgr->backlog,
+ &ifp->mgr->sctx->tcpquota, &ifp->tcplistensocket);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "creating TCP socket: %s",
+ isc_result_totext(result));
+ }
+
+ /*
+ * We call this now to update the tcp-highwater statistic:
+ * this is necessary because we are adding to the TCP quota just
+ * by listening.
+ */
+ result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "connecting TCP socket: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+/*
+ * XXXWPK we should probably pass a complete object with key, cert, and other
+ * TLS related options.
+ */
+static isc_result_t
+ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) {
+ isc_result_t result;
+
+ result = isc_nm_listentlsdns(
+ ifp->mgr->nm, &ifp->addr, ns__client_request, ifp,
+ ns__client_tcpconn, ifp, sizeof(ns_client_t), ifp->mgr->backlog,
+ &ifp->mgr->sctx->tcpquota, sslctx, &ifp->tcplistensocket);
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "creating TLS socket: %s",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ /*
+ * We call this now to update the tcp-highwater statistic:
+ * this is necessary because we are adding to the TCP quota just
+ * by listening.
+ */
+ result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "updating TCP stats: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+}
+
+#ifdef HAVE_LIBNGHTTP2
+static isc_result_t
+load_http_endpoints(isc_nm_http_endpoints_t *epset, ns_interface_t *ifp,
+ char **eps, size_t neps) {
+ isc_result_t result = ISC_R_FAILURE;
+
+ for (size_t i = 0; i < neps; i++) {
+ result = isc_nm_http_endpoints_add(epset, eps[i],
+ ns__client_request, ifp,
+ sizeof(ns_client_t));
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+
+ return (result);
+}
+#endif /* HAVE_LIBNGHTTP2 */
+
+static isc_result_t
+ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps,
+ size_t neps, uint32_t max_clients,
+ uint32_t max_concurrent_streams) {
+#if HAVE_LIBNGHTTP2
+ isc_result_t result = ISC_R_FAILURE;
+ isc_nmsocket_t *sock = NULL;
+ isc_nm_http_endpoints_t *epset = NULL;
+ isc_quota_t *quota = NULL;
+
+ epset = isc_nm_http_endpoints_new(ifp->mgr->mctx);
+
+ result = load_http_endpoints(epset, ifp, eps, neps);
+
+ if (result == ISC_R_SUCCESS) {
+ quota = isc_mem_get(ifp->mgr->mctx, sizeof(*quota));
+ isc_quota_init(quota, max_clients);
+ result = isc_nm_listenhttp(
+ ifp->mgr->nm, &ifp->addr, ifp->mgr->backlog, quota,
+ sslctx, epset, max_concurrent_streams, &sock);
+ }
+
+ isc_nm_http_endpoints_detach(&epset);
+
+ if (quota != NULL) {
+ if (result != ISC_R_SUCCESS) {
+ isc_quota_destroy(quota);
+ isc_mem_put(ifp->mgr->mctx, quota, sizeof(*quota));
+ } else {
+ ifp->http_quota = quota;
+ ns_server_append_http_quota(ifp->mgr->sctx, quota);
+ }
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "creating %s socket: %s",
+ sslctx ? "HTTPS" : "HTTP",
+ isc_result_totext(result));
+ return (result);
+ }
+
+ if (sslctx) {
+ ifp->http_secure_listensocket = sock;
+ } else {
+ ifp->http_listensocket = sock;
+ }
+
+ /*
+ * We call this now to update the tcp-highwater statistic:
+ * this is necessary because we are adding to the TCP quota just
+ * by listening.
+ */
+ result = ns__client_tcpconn(NULL, ISC_R_SUCCESS, ifp);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "updating TCP stats: %s",
+ isc_result_totext(result));
+ }
+
+ return (result);
+#else
+ UNUSED(ifp);
+ UNUSED(sslctx);
+ UNUSED(eps);
+ UNUSED(neps);
+ UNUSED(max_clients);
+ UNUSED(max_concurrent_streams);
+ return (ISC_R_NOTIMPLEMENTED);
+#endif
+}
+
+static isc_result_t
+interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, const char *name,
+ ns_interface_t **ifpret, ns_listenelt_t *elt,
+ bool *addr_in_use) {
+ isc_result_t result;
+ ns_interface_t *ifp = NULL;
+
+ REQUIRE(ifpret != NULL);
+ REQUIRE(addr_in_use == NULL || !*addr_in_use);
+
+ ifp = *ifpret;
+
+ if (ifp == NULL) {
+ interface_create(mgr, addr, name, &ifp);
+ } else {
+ REQUIRE(!LISTENING(ifp));
+ }
+
+ ifp->flags |= NS_INTERFACEFLAG_LISTENING;
+
+ if (elt->is_http) {
+ result = ns_interface_listenhttp(
+ ifp, elt->sslctx, elt->http_endpoints,
+ elt->http_endpoints_number, elt->http_max_clients,
+ elt->max_concurrent_streams);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_interface;
+ }
+ *ifpret = ifp;
+ return (result);
+ }
+
+ if (elt->sslctx != NULL) {
+ result = ns_interface_listentls(ifp, elt->sslctx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_interface;
+ }
+ *ifpret = ifp;
+ return (result);
+ }
+
+ result = ns_interface_listenudp(ifp);
+ if (result != ISC_R_SUCCESS) {
+ if ((result == ISC_R_ADDRINUSE) && (addr_in_use != NULL)) {
+ *addr_in_use = true;
+ }
+ goto cleanup_interface;
+ }
+
+ if (((mgr->sctx->options & NS_SERVER_NOTCP) == 0)) {
+ result = ns_interface_listentcp(ifp);
+ if (result != ISC_R_SUCCESS) {
+ if ((result == ISC_R_ADDRINUSE) &&
+ (addr_in_use != NULL))
+ {
+ *addr_in_use = true;
+ }
+
+ /*
+ * XXXRTH We don't currently have a way to easily stop
+ * dispatch service, so we currently return
+ * ISC_R_SUCCESS (the UDP stuff will work even if TCP
+ * creation failed). This will be fixed later.
+ */
+ result = ISC_R_SUCCESS;
+ }
+ }
+ *ifpret = ifp;
+ return (result);
+
+cleanup_interface:
+ ns_interface_shutdown(ifp);
+ return (result);
+}
+
+void
+ns_interface_shutdown(ns_interface_t *ifp) {
+ ifp->flags &= ~NS_INTERFACEFLAG_LISTENING;
+
+ if (ifp->udplistensocket != NULL) {
+ isc_nm_stoplistening(ifp->udplistensocket);
+ isc_nmsocket_close(&ifp->udplistensocket);
+ }
+ if (ifp->tcplistensocket != NULL) {
+ isc_nm_stoplistening(ifp->tcplistensocket);
+ isc_nmsocket_close(&ifp->tcplistensocket);
+ }
+ if (ifp->http_listensocket != NULL) {
+ isc_nm_stoplistening(ifp->http_listensocket);
+ isc_nmsocket_close(&ifp->http_listensocket);
+ }
+ if (ifp->http_secure_listensocket != NULL) {
+ isc_nm_stoplistening(ifp->http_secure_listensocket);
+ isc_nmsocket_close(&ifp->http_secure_listensocket);
+ }
+ ifp->http_quota = NULL;
+}
+
+static void
+interface_destroy(ns_interface_t **interfacep) {
+ ns_interface_t *ifp = NULL;
+ ns_interfacemgr_t *mgr = NULL;
+
+ REQUIRE(interfacep != NULL);
+
+ ifp = *interfacep;
+ *interfacep = NULL;
+
+ REQUIRE(NS_INTERFACE_VALID(ifp));
+
+ mgr = ifp->mgr;
+
+ ns_interface_shutdown(ifp);
+
+ ifp->magic = 0;
+ isc_mutex_destroy(&ifp->lock);
+ ns_interfacemgr_detach(&ifp->mgr);
+ isc_refcount_destroy(&ifp->ntcpactive);
+ isc_refcount_destroy(&ifp->ntcpaccepting);
+
+ isc_mem_put(mgr->mctx, ifp, sizeof(*ifp));
+}
+
+/*%
+ * Search the interface list for an interface whose address and port
+ * both match those of 'addr'. Return a pointer to it, or NULL if not found.
+ */
+static ns_interface_t *
+find_matching_interface(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr) {
+ ns_interface_t *ifp;
+ LOCK(&mgr->lock);
+ for (ifp = ISC_LIST_HEAD(mgr->interfaces); ifp != NULL;
+ ifp = ISC_LIST_NEXT(ifp, link))
+ {
+ if (isc_sockaddr_equal(&ifp->addr, addr)) {
+ break;
+ }
+ }
+ UNLOCK(&mgr->lock);
+ return (ifp);
+}
+
+/*%
+ * Remove any interfaces whose generation number is not the current one.
+ */
+static void
+purge_old_interfaces(ns_interfacemgr_t *mgr) {
+ ns_interface_t *ifp = NULL, *next = NULL;
+ ISC_LIST(ns_interface_t) interfaces;
+
+ ISC_LIST_INIT(interfaces);
+
+ LOCK(&mgr->lock);
+ for (ifp = ISC_LIST_HEAD(mgr->interfaces); ifp != NULL; ifp = next) {
+ INSIST(NS_INTERFACE_VALID(ifp));
+ next = ISC_LIST_NEXT(ifp, link);
+ if (ifp->generation != mgr->generation) {
+ ISC_LIST_UNLINK(ifp->mgr->interfaces, ifp, link);
+ ISC_LIST_APPEND(interfaces, ifp, link);
+ }
+ }
+ UNLOCK(&mgr->lock);
+
+ for (ifp = ISC_LIST_HEAD(interfaces); ifp != NULL; ifp = next) {
+ next = ISC_LIST_NEXT(ifp, link);
+ if (LISTENING(ifp)) {
+ char sabuf[256];
+ isc_sockaddr_format(&ifp->addr, sabuf, sizeof(sabuf));
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
+ "no longer listening on %s", sabuf);
+ ns_interface_shutdown(ifp);
+ }
+ ISC_LIST_UNLINK(interfaces, ifp, link);
+ interface_destroy(&ifp);
+ }
+}
+
+static bool
+listenon_is_ip6_any(ns_listenelt_t *elt) {
+ REQUIRE(elt && elt->acl);
+ return (dns_acl_isany(elt->acl));
+}
+
+static isc_result_t
+setup_locals(isc_interface_t *interface, dns_acl_t *localhost,
+ dns_acl_t *localnets) {
+ isc_result_t result;
+ unsigned int prefixlen;
+ isc_netaddr_t *netaddr;
+
+ netaddr = &interface->address;
+
+ /* First add localhost address */
+ prefixlen = (netaddr->family == AF_INET) ? 32 : 128;
+ result = dns_iptable_addprefix(localhost->iptable, netaddr, prefixlen,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /* Then add localnets prefix */
+ result = isc_netaddr_masktoprefixlen(&interface->netmask, &prefixlen);
+
+ /* Non contiguous netmasks not allowed by IPv6 arch. */
+ if (result != ISC_R_SUCCESS && netaddr->family == AF_INET6) {
+ return (result);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING,
+ "omitting IPv4 interface %s from "
+ "localnets ACL: %s",
+ interface->name, isc_result_totext(result));
+ return (ISC_R_SUCCESS);
+ }
+
+ if (prefixlen == 0U) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING,
+ "omitting %s interface %s from localnets ACL: "
+ "zero prefix length detected",
+ (netaddr->family == AF_INET) ? "IPv4" : "IPv6",
+ interface->name);
+ return (ISC_R_SUCCESS);
+ }
+
+ result = dns_iptable_addprefix(localnets->iptable, netaddr, prefixlen,
+ true);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+setup_listenon(ns_interfacemgr_t *mgr, isc_interface_t *interface,
+ in_port_t port) {
+ isc_sockaddr_t *addr;
+ isc_sockaddr_t *old;
+
+ addr = isc_mem_get(mgr->mctx, sizeof(*addr));
+
+ isc_sockaddr_fromnetaddr(addr, &interface->address, port);
+
+ LOCK(&mgr->lock);
+ for (old = ISC_LIST_HEAD(mgr->listenon); old != NULL;
+ old = ISC_LIST_NEXT(old, link))
+ {
+ if (isc_sockaddr_equal(addr, old)) {
+ /* We found an existing address */
+ isc_mem_put(mgr->mctx, addr, sizeof(*addr));
+ goto unlock;
+ }
+ }
+
+ ISC_LIST_APPEND(mgr->listenon, addr, link);
+unlock:
+ UNLOCK(&mgr->lock);
+}
+
+static void
+clearlistenon(ns_interfacemgr_t *mgr) {
+ ISC_LIST(isc_sockaddr_t) listenon;
+ isc_sockaddr_t *old;
+
+ ISC_LIST_INIT(listenon);
+
+ LOCK(&mgr->lock);
+ ISC_LIST_MOVE(listenon, mgr->listenon);
+ UNLOCK(&mgr->lock);
+
+ old = ISC_LIST_HEAD(listenon);
+ while (old != NULL) {
+ ISC_LIST_UNLINK(listenon, old, link);
+ isc_mem_put(mgr->mctx, old, sizeof(*old));
+ old = ISC_LIST_HEAD(listenon);
+ }
+}
+
+static void
+replace_listener_tlsctx(ns_interface_t *ifp, isc_tlsctx_t *newctx) {
+ char sabuf[ISC_SOCKADDR_FORMATSIZE];
+
+ isc_sockaddr_format(&ifp->addr, sabuf, sizeof(sabuf));
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
+ "updating TLS context on %s", sabuf);
+ if (ifp->tcplistensocket != NULL) {
+ /* 'tcplistensocket' is used for DoT */
+ isc_nmsocket_set_tlsctx(ifp->tcplistensocket, newctx);
+ } else if (ifp->http_secure_listensocket != NULL) {
+ isc_nmsocket_set_tlsctx(ifp->http_secure_listensocket, newctx);
+ }
+}
+
+#ifdef HAVE_LIBNGHTTP2
+static void
+update_http_settings(ns_interface_t *ifp, ns_listenelt_t *le) {
+ isc_result_t result;
+ isc_nmsocket_t *listener;
+ isc_nm_http_endpoints_t *epset;
+
+ REQUIRE(le->is_http);
+
+ INSIST(ifp->http_quota != NULL);
+ isc_quota_max(ifp->http_quota, le->http_max_clients);
+
+ if (ifp->http_secure_listensocket != NULL) {
+ listener = ifp->http_secure_listensocket;
+ } else {
+ INSIST(ifp->http_listensocket != NULL);
+ listener = ifp->http_listensocket;
+ }
+
+ isc_nmsocket_set_max_streams(listener, le->max_concurrent_streams);
+
+ epset = isc_nm_http_endpoints_new(ifp->mgr->mctx);
+
+ result = load_http_endpoints(epset, ifp, le->http_endpoints,
+ le->http_endpoints_number);
+
+ if (result == ISC_R_SUCCESS) {
+ isc_nm_http_set_endpoints(listener, epset);
+ }
+
+ isc_nm_http_endpoints_detach(&epset);
+}
+#endif /* HAVE_LIBNGHTTP2 */
+
+static void
+update_listener_configuration(ns_interfacemgr_t *mgr, ns_interface_t *ifp,
+ ns_listenelt_t *le) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ REQUIRE(NS_INTERFACE_VALID(ifp));
+ REQUIRE(le != NULL);
+
+ LOCK(&mgr->lock);
+ /*
+ * We need to update the TLS contexts
+ * inside the TLS/HTTPS listeners during
+ * a reconfiguration because the
+ * certificates could have been changed.
+ */
+ if (le->sslctx != NULL) {
+ replace_listener_tlsctx(ifp, le->sslctx);
+ }
+
+#ifdef HAVE_LIBNGHTTP2
+ /*
+ * Let's update HTTP listener settings
+ * on reconfiguration.
+ */
+ if (le->is_http) {
+ update_http_settings(ifp, le);
+ }
+#endif /* HAVE_LIBNGHTTP2 */
+
+ UNLOCK(&mgr->lock);
+}
+
+static isc_result_t
+do_scan(ns_interfacemgr_t *mgr, bool verbose, bool config) {
+ isc_interfaceiter_t *iter = NULL;
+ bool scan_ipv4 = false;
+ bool scan_ipv6 = false;
+ bool ipv6only = true;
+ bool ipv6pktinfo = true;
+ isc_result_t result;
+ isc_netaddr_t zero_address, zero_address6;
+ ns_listenelt_t *le = NULL;
+ isc_sockaddr_t listen_addr;
+ ns_interface_t *ifp = NULL;
+ bool log_explicit = false;
+ bool dolistenon;
+ char sabuf[ISC_SOCKADDR_FORMATSIZE];
+ bool tried_listening;
+ bool all_addresses_in_use;
+ dns_acl_t *localhost = NULL;
+ dns_acl_t *localnets = NULL;
+
+ if (isc_net_probeipv6() == ISC_R_SUCCESS) {
+ scan_ipv6 = true;
+ } else if ((mgr->sctx->options & NS_SERVER_DISABLE6) == 0) {
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1),
+ "no IPv6 interfaces found");
+ }
+
+ if (isc_net_probeipv4() == ISC_R_SUCCESS) {
+ scan_ipv4 = true;
+ } else if ((mgr->sctx->options & NS_SERVER_DISABLE4) == 0) {
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ verbose ? ISC_LOG_INFO : ISC_LOG_DEBUG(1),
+ "no IPv4 interfaces found");
+ }
+
+ /*
+ * A special, but typical case; listen-on-v6 { any; }.
+ * When we can make the socket IPv6-only, open a single wildcard
+ * socket for IPv6 communication. Otherwise, make separate
+ * socket for each IPv6 address in order to avoid accepting IPv4
+ * packets as the form of mapped addresses unintentionally
+ * unless explicitly allowed.
+ */
+ if (scan_ipv6 && isc_net_probe_ipv6only() != ISC_R_SUCCESS) {
+ ipv6only = false;
+ log_explicit = true;
+ }
+ if (scan_ipv6 && isc_net_probe_ipv6pktinfo() != ISC_R_SUCCESS) {
+ ipv6pktinfo = false;
+ log_explicit = true;
+ }
+ if (scan_ipv6 && ipv6only && ipv6pktinfo) {
+ for (le = ISC_LIST_HEAD(mgr->listenon6->elts); le != NULL;
+ le = ISC_LIST_NEXT(le, link))
+ {
+ struct in6_addr in6a;
+
+ if (!listenon_is_ip6_any(le)) {
+ continue;
+ }
+
+ in6a = in6addr_any;
+ isc_sockaddr_fromin6(&listen_addr, &in6a, le->port);
+
+ ifp = find_matching_interface(mgr, &listen_addr);
+ if (ifp != NULL) {
+ ifp->generation = mgr->generation;
+ if (LISTENING(ifp)) {
+ if (config) {
+ update_listener_configuration(
+ mgr, ifp, le);
+ }
+ continue;
+ }
+ }
+
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
+ "listening on IPv6 "
+ "interfaces, port %u",
+ le->port);
+ result = interface_setup(mgr, &listen_addr, "<any>",
+ &ifp, le, NULL);
+ if (result == ISC_R_SUCCESS) {
+ ifp->flags |= NS_INTERFACEFLAG_ANYADDR;
+ } else {
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ ISC_LOG_ERROR,
+ "listening on all IPv6 "
+ "interfaces failed");
+ }
+ /* Continue. */
+ }
+ }
+
+ isc_netaddr_any(&zero_address);
+ isc_netaddr_any6(&zero_address6);
+
+ result = isc_interfaceiter_create(mgr->mctx, &iter);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_acl_create(mgr->mctx, 0, &localhost);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_iter;
+ }
+ result = dns_acl_create(mgr->mctx, 0, &localnets);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_localhost;
+ }
+
+ clearlistenon(mgr);
+
+ tried_listening = false;
+ all_addresses_in_use = true;
+ for (result = isc_interfaceiter_first(iter); result == ISC_R_SUCCESS;
+ result = isc_interfaceiter_next(iter))
+ {
+ isc_interface_t interface;
+ ns_listenlist_t *ll = NULL;
+ unsigned int family;
+
+ result = isc_interfaceiter_current(iter, &interface);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+
+ family = interface.address.family;
+ if (family != AF_INET && family != AF_INET6) {
+ continue;
+ }
+ if (!scan_ipv4 && family == AF_INET) {
+ continue;
+ }
+ if (!scan_ipv6 && family == AF_INET6) {
+ continue;
+ }
+
+ /*
+ * Test for the address being nonzero rather than testing
+ * INTERFACE_F_UP, because on some systems the latter
+ * follows the media state and we could end up ignoring
+ * the interface for an entire rescan interval due to
+ * a temporary media glitch at rescan time.
+ */
+ if (family == AF_INET &&
+ isc_netaddr_equal(&interface.address, &zero_address))
+ {
+ continue;
+ }
+ if (family == AF_INET6 &&
+ isc_netaddr_equal(&interface.address, &zero_address6))
+ {
+ continue;
+ }
+
+ /*
+ * If running with -T fixedlocal, then we only
+ * want 127.0.0.1 and ::1 in the localhost ACL.
+ */
+ if (((mgr->sctx->options & NS_SERVER_FIXEDLOCAL) != 0) &&
+ !isc_netaddr_isloopback(&interface.address))
+ {
+ goto listenon;
+ }
+
+ result = setup_locals(&interface, localhost, localnets);
+ if (result != ISC_R_SUCCESS) {
+ goto ignore_interface;
+ }
+
+ listenon:
+ ll = (family == AF_INET) ? mgr->listenon4 : mgr->listenon6;
+ dolistenon = true;
+ for (le = ISC_LIST_HEAD(ll->elts); le != NULL;
+ le = ISC_LIST_NEXT(le, link))
+ {
+ int match;
+ bool addr_in_use = false;
+ bool ipv6_wildcard = false;
+ isc_sockaddr_t listen_sockaddr;
+
+ isc_sockaddr_fromnetaddr(&listen_sockaddr,
+ &interface.address, le->port);
+
+ /*
+ * See if the address matches the listen-on statement;
+ * if not, ignore the interface, but store it in
+ * the interface table so we know we've seen it
+ * before.
+ */
+ (void)dns_acl_match(&interface.address, NULL, le->acl,
+ mgr->aclenv, &match, NULL);
+ if (match <= 0) {
+ ns_interface_t *new = NULL;
+ interface_create(mgr, &listen_sockaddr,
+ interface.name, &new);
+ continue;
+ }
+
+ if (dolistenon) {
+ setup_listenon(mgr, &interface, le->port);
+ dolistenon = false;
+ }
+
+ /*
+ * The case of "any" IPv6 address will require
+ * special considerations later, so remember it.
+ */
+ if (family == AF_INET6 && ipv6only && ipv6pktinfo &&
+ listenon_is_ip6_any(le))
+ {
+ ipv6_wildcard = true;
+ }
+
+ ifp = find_matching_interface(mgr, &listen_sockaddr);
+ if (ifp != NULL) {
+ ifp->generation = mgr->generation;
+ if (LISTENING(ifp)) {
+ if (config) {
+ update_listener_configuration(
+ mgr, ifp, le);
+ }
+ continue;
+ }
+ }
+
+ if (ipv6_wildcard) {
+ continue;
+ }
+
+ if (log_explicit && family == AF_INET6 &&
+ listenon_is_ip6_any(le))
+ {
+ isc_log_write(IFMGR_COMMON_LOGARGS,
+ verbose ? ISC_LOG_INFO
+ : ISC_LOG_DEBUG(1),
+ "IPv6 socket API is "
+ "incomplete; explicitly "
+ "binding to each IPv6 "
+ "address separately");
+ log_explicit = false;
+ }
+ isc_sockaddr_format(&listen_sockaddr, sabuf,
+ sizeof(sabuf));
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_INFO,
+ "listening on %s interface "
+ "%s, %s",
+ (family == AF_INET) ? "IPv4" : "IPv6",
+ interface.name, sabuf);
+
+ result = interface_setup(mgr, &listen_sockaddr,
+ interface.name, &ifp, le,
+ &addr_in_use);
+
+ tried_listening = true;
+ if (!addr_in_use) {
+ all_addresses_in_use = false;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(
+ IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "creating %s interface "
+ "%s failed; interface ignored",
+ (family == AF_INET) ? "IPv4" : "IPv6",
+ interface.name);
+ }
+ /* Continue. */
+ }
+ continue;
+
+ ignore_interface:
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_ERROR,
+ "ignoring %s interface %s: %s",
+ (family == AF_INET) ? "IPv4" : "IPv6",
+ interface.name, isc_result_totext(result));
+ continue;
+ }
+ if (result != ISC_R_NOMORE) {
+ UNEXPECTED_ERROR("interface iteration failed: %s",
+ isc_result_totext(result));
+ } else {
+ result = ((tried_listening && all_addresses_in_use)
+ ? ISC_R_ADDRINUSE
+ : ISC_R_SUCCESS);
+ }
+
+ dns_aclenv_set(mgr->aclenv, localhost, localnets);
+
+ /* cleanup_localnets: */
+ dns_acl_detach(&localnets);
+
+cleanup_localhost:
+ dns_acl_detach(&localhost);
+
+cleanup_iter:
+ isc_interfaceiter_destroy(&iter);
+ return (result);
+}
+
+isc_result_t
+ns_interfacemgr_scan(ns_interfacemgr_t *mgr, bool verbose, bool config) {
+ isc_result_t result;
+ bool purge = true;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ REQUIRE(isc_nm_tid() == 0);
+
+ mgr->generation++; /* Increment the generation count. */
+
+ result = do_scan(mgr, verbose, config);
+ if ((result != ISC_R_SUCCESS) && (result != ISC_R_ADDRINUSE)) {
+ purge = false;
+ }
+
+ /*
+ * Now go through the interface list and delete anything that
+ * does not have the current generation number. This is
+ * how we catch interfaces that go away or change their
+ * addresses.
+ */
+ if (purge) {
+ purge_old_interfaces(mgr);
+ }
+
+ /*
+ * Warn if we are not listening on any interface.
+ */
+ if (ISC_LIST_EMPTY(mgr->interfaces)) {
+ isc_log_write(IFMGR_COMMON_LOGARGS, ISC_LOG_WARNING,
+ "not listening on any interfaces");
+ }
+
+ return (result);
+}
+
+bool
+ns_interfacemgr_islistening(ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ return (ISC_LIST_EMPTY(mgr->interfaces) ? false : true);
+}
+
+void
+ns_interfacemgr_setlistenon4(ns_interfacemgr_t *mgr, ns_listenlist_t *value) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ LOCK(&mgr->lock);
+ ns_listenlist_detach(&mgr->listenon4);
+ ns_listenlist_attach(value, &mgr->listenon4);
+ UNLOCK(&mgr->lock);
+}
+
+void
+ns_interfacemgr_setlistenon6(ns_interfacemgr_t *mgr, ns_listenlist_t *value) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ LOCK(&mgr->lock);
+ ns_listenlist_detach(&mgr->listenon6);
+ ns_listenlist_attach(value, &mgr->listenon6);
+ UNLOCK(&mgr->lock);
+}
+
+void
+ns_interfacemgr_dumprecursing(FILE *f, ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ LOCK(&mgr->lock);
+ for (size_t i = 0; i < (size_t)mgr->ncpus; i++) {
+ ns_client_dumprecursing(f, mgr->clientmgrs[i]);
+ }
+ UNLOCK(&mgr->lock);
+}
+
+bool
+ns_interfacemgr_listeningon(ns_interfacemgr_t *mgr,
+ const isc_sockaddr_t *addr) {
+ isc_sockaddr_t *old;
+ bool result = false;
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ /*
+ * If the manager is shutting down it's safer to
+ * return true.
+ */
+ if (atomic_load(&mgr->shuttingdown)) {
+ return (true);
+ }
+ LOCK(&mgr->lock);
+ for (old = ISC_LIST_HEAD(mgr->listenon); old != NULL;
+ old = ISC_LIST_NEXT(old, link))
+ {
+ if (isc_sockaddr_equal(old, addr)) {
+ result = true;
+ break;
+ }
+ }
+ UNLOCK(&mgr->lock);
+
+ return (result);
+}
+
+ns_server_t *
+ns_interfacemgr_getserver(ns_interfacemgr_t *mgr) {
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+
+ return (mgr->sctx);
+}
+
+ns_clientmgr_t *
+ns_interfacemgr_getclientmgr(ns_interfacemgr_t *mgr) {
+ int tid = isc_nm_tid();
+
+ REQUIRE(NS_INTERFACEMGR_VALID(mgr));
+ REQUIRE(tid >= 0);
+ REQUIRE(tid < mgr->ncpus);
+
+ return (mgr->clientmgrs[tid]);
+}
diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c
new file mode 100644
index 0000000..c0f9e59
--- /dev/null
+++ b/lib/ns/listenlist.c
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+
+#include <ns/listenlist.h>
+
+static void
+destroy(ns_listenlist_t *list);
+
+static isc_result_t
+listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
+ const uint16_t family, const bool is_http, bool tls,
+ const ns_listen_tls_params_t *tls_params,
+ isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) {
+ ns_listenelt_t *elt = NULL;
+ isc_result_t result = ISC_R_SUCCESS;
+ isc_tlsctx_t *sslctx = NULL;
+ isc_tls_cert_store_t *store = NULL, *found_store = NULL;
+
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(!tls || (tls_params != NULL && tlsctx_cache != NULL));
+
+ if (tls) {
+ const isc_tlsctx_cache_transport_t transport =
+ is_http ? isc_tlsctx_cache_https : isc_tlsctx_cache_tls;
+
+ /*
+ * Let's try to reuse the existing context from the cache in
+ * order to avoid excessive TLS contexts creation.
+ */
+ result = isc_tlsctx_cache_find(tlsctx_cache, tls_params->name,
+ transport, family, &sslctx,
+ &found_store, NULL);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * The lookup failed, let's try to create a new context
+ * and store it within the cache.
+ */
+ INSIST(tls_params->name != NULL &&
+ *tls_params->name != '\0');
+
+ result = isc_tlsctx_createserver(
+ tls_params->key, tls_params->cert, &sslctx);
+ if (result != ISC_R_SUCCESS) {
+ goto tls_error;
+ }
+
+ /*
+ * We need to initialise session ID context to make TLS
+ * session resumption work correctly - in particular in
+ * the case when client certificates are used (Mutual
+ * TLS) - otherwise resumption attempts will lead to
+ * handshake failures. See OpenSSL documentation for
+ * 'SSL_CTX_set_session_id_context()', the "Warnings"
+ * section.
+ */
+ isc_tlsctx_set_random_session_id_context(sslctx);
+
+ /*
+ * If CA-bundle file is specified - enable client
+ * certificates validation.
+ */
+ if (tls_params->ca_file != NULL) {
+ if (found_store == NULL) {
+ result = isc_tls_cert_store_create(
+ tls_params->ca_file, &store);
+ if (result != ISC_R_SUCCESS) {
+ goto tls_error;
+ }
+ } else {
+ store = found_store;
+ }
+
+ result = isc_tlsctx_enable_peer_verification(
+ sslctx, true, store, NULL, false);
+ if (result != ISC_R_SUCCESS) {
+ goto tls_error;
+ }
+
+ /*
+ * Load the list of allowed client certificate
+ * issuers to send to TLS clients.
+ */
+ result = isc_tlsctx_load_client_ca_names(
+ sslctx, tls_params->ca_file);
+ if (result != ISC_R_SUCCESS) {
+ goto tls_error;
+ }
+ }
+
+ if (tls_params->protocols != 0) {
+ isc_tlsctx_set_protocols(sslctx,
+ tls_params->protocols);
+ }
+
+ if (tls_params->dhparam_file != NULL) {
+ if (!isc_tlsctx_load_dhparams(
+ sslctx, tls_params->dhparam_file))
+ {
+ result = ISC_R_FAILURE;
+ goto tls_error;
+ }
+ }
+
+ if (tls_params->ciphers != NULL) {
+ isc_tlsctx_set_cipherlist(sslctx,
+ tls_params->ciphers);
+ }
+
+ if (tls_params->prefer_server_ciphers_set) {
+ isc_tlsctx_prefer_server_ciphers(
+ sslctx,
+ tls_params->prefer_server_ciphers);
+ }
+
+ if (tls_params->session_tickets_set) {
+ isc_tlsctx_session_tickets(
+ sslctx, tls_params->session_tickets);
+ }
+
+#ifdef HAVE_LIBNGHTTP2
+ if (is_http) {
+ isc_tlsctx_enable_http2server_alpn(sslctx);
+ }
+#endif /* HAVE_LIBNGHTTP2 */
+
+ if (!is_http) {
+ isc_tlsctx_enable_dot_server_alpn(sslctx);
+ }
+
+ /*
+ * The storing in the cache should not fail because the
+ * (re)initialisation happens from within a single
+ * thread.
+ *
+ * Taking into account that the most recent call to
+ * 'isc_tlsctx_cache_find()' has failed, it means that
+ * the TLS context has not been found. Considering that
+ * the initialisation happens from within the context of
+ * a single thread, the call to 'isc_tlsctx_cache_add()'
+ * is expected not to fail.
+ */
+ RUNTIME_CHECK(isc_tlsctx_cache_add(
+ tlsctx_cache, tls_params->name,
+ transport, family, sslctx, store,
+ NULL, NULL, NULL,
+ NULL) == ISC_R_SUCCESS);
+ } else {
+ INSIST(sslctx != NULL);
+ }
+ }
+
+ elt = isc_mem_get(mctx, sizeof(*elt));
+ elt->mctx = mctx;
+ ISC_LINK_INIT(elt, link);
+ elt->port = port;
+ elt->is_http = false;
+ elt->acl = acl;
+ elt->sslctx = sslctx;
+ elt->sslctx_cache = NULL;
+ if (sslctx != NULL && tlsctx_cache != NULL) {
+ isc_tlsctx_cache_attach(tlsctx_cache, &elt->sslctx_cache);
+ }
+ elt->http_endpoints = NULL;
+ elt->http_endpoints_number = 0;
+ elt->http_max_clients = 0;
+ elt->max_concurrent_streams = 0;
+
+ *target = elt;
+ return (ISC_R_SUCCESS);
+tls_error:
+ if (sslctx != NULL) {
+ isc_tlsctx_free(&sslctx);
+ }
+
+ if (store != NULL && store != found_store) {
+ isc_tls_cert_store_free(&store);
+ }
+ return (result);
+}
+
+isc_result_t
+ns_listenelt_create(isc_mem_t *mctx, in_port_t port, dns_acl_t *acl,
+ const uint16_t family, bool tls,
+ const ns_listen_tls_params_t *tls_params,
+ isc_tlsctx_cache_t *tlsctx_cache, ns_listenelt_t **target) {
+ return listenelt_create(mctx, port, acl, family, false, tls, tls_params,
+ tlsctx_cache, target);
+}
+
+isc_result_t
+ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, dns_acl_t *acl,
+ const uint16_t family, bool tls,
+ const ns_listen_tls_params_t *tls_params,
+ isc_tlsctx_cache_t *tlsctx_cache, char **endpoints,
+ size_t nendpoints, const uint32_t max_clients,
+ const uint32_t max_streams, ns_listenelt_t **target) {
+ isc_result_t result;
+
+ REQUIRE(target != NULL && *target == NULL);
+ REQUIRE(endpoints != NULL && *endpoints != NULL);
+ REQUIRE(nendpoints > 0);
+
+ result = listenelt_create(mctx, http_port, acl, family, true, tls,
+ tls_params, tlsctx_cache, target);
+ if (result == ISC_R_SUCCESS) {
+ (*target)->is_http = true;
+ (*target)->http_endpoints = endpoints;
+ (*target)->http_endpoints_number = nendpoints;
+ /*
+ * 0 sized quota - means unlimited quota. We used to not
+ * create a quota object in such a case, but we might need to
+ * update the value of the quota during reconfiguration, so we
+ * need to have a quota object in place anyway.
+ */
+ (*target)->http_max_clients = max_clients == 0 ? UINT32_MAX
+ : max_clients;
+ (*target)->max_concurrent_streams = max_streams;
+ } else {
+ size_t i;
+ for (i = 0; i < nendpoints; i++) {
+ isc_mem_free(mctx, endpoints[i]);
+ }
+ isc_mem_free(mctx, endpoints);
+ }
+ return (result);
+}
+
+void
+ns_listenelt_destroy(ns_listenelt_t *elt) {
+ if (elt->acl != NULL) {
+ dns_acl_detach(&elt->acl);
+ }
+
+ elt->sslctx = NULL; /* this one is going to be destroyed alongside the
+ sslctx_cache */
+ if (elt->sslctx_cache != NULL) {
+ isc_tlsctx_cache_detach(&elt->sslctx_cache);
+ }
+ if (elt->http_endpoints != NULL) {
+ size_t i;
+ INSIST(elt->http_endpoints_number > 0);
+ for (i = 0; i < elt->http_endpoints_number; i++) {
+ isc_mem_free(elt->mctx, elt->http_endpoints[i]);
+ }
+ isc_mem_free(elt->mctx, elt->http_endpoints);
+ }
+ isc_mem_put(elt->mctx, elt, sizeof(*elt));
+}
+
+isc_result_t
+ns_listenlist_create(isc_mem_t *mctx, ns_listenlist_t **target) {
+ ns_listenlist_t *list = NULL;
+ REQUIRE(target != NULL && *target == NULL);
+ list = isc_mem_get(mctx, sizeof(*list));
+ list->mctx = mctx;
+ list->refcount = 1;
+ ISC_LIST_INIT(list->elts);
+ *target = list;
+ return (ISC_R_SUCCESS);
+}
+
+static void
+destroy(ns_listenlist_t *list) {
+ ns_listenelt_t *elt, *next;
+ for (elt = ISC_LIST_HEAD(list->elts); elt != NULL; elt = next) {
+ next = ISC_LIST_NEXT(elt, link);
+ ns_listenelt_destroy(elt);
+ }
+ isc_mem_put(list->mctx, list, sizeof(*list));
+}
+
+void
+ns_listenlist_attach(ns_listenlist_t *source, ns_listenlist_t **target) {
+ INSIST(source->refcount > 0);
+ source->refcount++;
+ *target = source;
+}
+
+void
+ns_listenlist_detach(ns_listenlist_t **listp) {
+ ns_listenlist_t *list = *listp;
+ *listp = NULL;
+ INSIST(list->refcount > 0);
+ list->refcount--;
+ if (list->refcount == 0) {
+ destroy(list);
+ }
+}
+
+isc_result_t
+ns_listenlist_default(isc_mem_t *mctx, in_port_t port, bool enabled,
+ const uint16_t family, ns_listenlist_t **target) {
+ isc_result_t result;
+ dns_acl_t *acl = NULL;
+ ns_listenelt_t *elt = NULL;
+ ns_listenlist_t *list = NULL;
+
+ REQUIRE(target != NULL && *target == NULL);
+ if (enabled) {
+ result = dns_acl_any(mctx, &acl);
+ } else {
+ result = dns_acl_none(mctx, &acl);
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = ns_listenelt_create(mctx, port, acl, family, false, NULL, NULL,
+ &elt);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_acl;
+ }
+
+ result = ns_listenlist_create(mctx, &list);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup_listenelt;
+ }
+
+ ISC_LIST_APPEND(list->elts, elt, link);
+
+ *target = list;
+ return (ISC_R_SUCCESS);
+
+cleanup_listenelt:
+ ns_listenelt_destroy(elt);
+cleanup_acl:
+ dns_acl_detach(&acl);
+cleanup:
+ return (result);
+}
diff --git a/lib/ns/log.c b/lib/ns/log.c
new file mode 100644
index 0000000..4a7ff20
--- /dev/null
+++ b/lib/ns/log.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <ns/log.h>
+
+#ifndef ISC_FACILITY
+#define ISC_FACILITY LOG_DAEMON
+#endif /* ifndef ISC_FACILITY */
+
+/*%
+ * When adding a new category, be sure to add the appropriate
+ * \#define to <ns/log.h>
+ */
+isc_logcategory_t ns_categories[] = { { "client", 0 },
+ { "network", 0 },
+ { "update", 0 },
+ { "queries", 0 },
+ { "update-security", 0 },
+ { "query-errors", 0 },
+ { "trust-anchor-telemetry", 0 },
+ { "serve-stale", 0 },
+ { NULL, 0 } };
+
+/*%
+ * When adding a new module, be sure to add the appropriate
+ * \#define to <ns/log.h>.
+ */
+isc_logmodule_t ns_modules[] = {
+ { "ns/client", 0 }, { "ns/query", 0 }, { "ns/interfacemgr", 0 },
+ { "ns/update", 0 }, { "ns/xfer-in", 0 }, { "ns/xfer-out", 0 },
+ { "ns/notify", 0 }, { "ns/hooks", 0 }, { NULL, 0 }
+};
+
+isc_log_t *ns_lctx = NULL;
+
+void
+ns_log_init(isc_log_t *lctx) {
+ REQUIRE(lctx != NULL);
+
+ isc_log_registercategories(lctx, ns_categories);
+ isc_log_registermodules(lctx, ns_modules);
+}
+
+void
+ns_log_setcontext(isc_log_t *lctx) {
+ ns_lctx = lctx;
+}
diff --git a/lib/ns/notify.c b/lib/ns/notify.c
new file mode 100644
index 0000000..35b6b91
--- /dev/null
+++ b/lib/ns/notify.c
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/log.h>
+#include <isc/print.h>
+#include <isc/result.h>
+
+#include <dns/message.h>
+#include <dns/rdataset.h>
+#include <dns/result.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include <ns/log.h>
+#include <ns/notify.h>
+#include <ns/types.h>
+
+/*! \file
+ * \brief
+ * This module implements notify as in RFC1996.
+ */
+
+static void
+notify_log(ns_client_t *client, int level, const char *fmt, ...) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ ns_client_logv(client, DNS_LOGCATEGORY_NOTIFY, NS_LOGMODULE_NOTIFY,
+ level, fmt, ap);
+ va_end(ap);
+}
+
+static void
+respond(ns_client_t *client, isc_result_t result) {
+ dns_rcode_t rcode;
+ dns_message_t *message;
+ isc_result_t msg_result;
+
+ message = client->message;
+ rcode = dns_result_torcode(result);
+
+ msg_result = dns_message_reply(message, true);
+ if (msg_result != ISC_R_SUCCESS) {
+ msg_result = dns_message_reply(message, false);
+ }
+ if (msg_result != ISC_R_SUCCESS) {
+ ns_client_drop(client, msg_result);
+ isc_nmhandle_detach(&client->reqhandle);
+ return;
+ }
+ message->rcode = rcode;
+ if (rcode == dns_rcode_noerror) {
+ message->flags |= DNS_MESSAGEFLAG_AA;
+ } else {
+ message->flags &= ~DNS_MESSAGEFLAG_AA;
+ }
+
+ ns_client_send(client);
+ isc_nmhandle_detach(&client->reqhandle);
+}
+
+void
+ns_notify_start(ns_client_t *client, isc_nmhandle_t *handle) {
+ dns_message_t *request = client->message;
+ isc_result_t result;
+ dns_name_t *zonename;
+ dns_rdataset_t *zone_rdataset;
+ dns_zone_t *zone = NULL;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char tsigbuf[DNS_NAME_FORMATSIZE * 2 + sizeof(": TSIG '' ()")];
+ dns_tsigkey_t *tsigkey;
+
+ /*
+ * Attach to the request handle
+ */
+ isc_nmhandle_attach(handle, &client->reqhandle);
+
+ /*
+ * Interpret the question section.
+ */
+ result = dns_message_firstname(request, DNS_SECTION_QUESTION);
+ if (result != ISC_R_SUCCESS) {
+ notify_log(client, ISC_LOG_NOTICE,
+ "notify question section empty");
+ result = DNS_R_FORMERR;
+ goto done;
+ }
+
+ /*
+ * The question section must contain exactly one question.
+ */
+ zonename = NULL;
+ dns_message_currentname(request, DNS_SECTION_QUESTION, &zonename);
+ zone_rdataset = ISC_LIST_HEAD(zonename->list);
+ if (ISC_LIST_NEXT(zone_rdataset, link) != NULL) {
+ notify_log(client, ISC_LOG_NOTICE,
+ "notify question section contains multiple RRs");
+ result = DNS_R_FORMERR;
+ goto done;
+ }
+
+ /* The zone section must have exactly one name. */
+ result = dns_message_nextname(request, DNS_SECTION_ZONE);
+ if (result != ISC_R_NOMORE) {
+ notify_log(client, ISC_LOG_NOTICE,
+ "notify question section contains multiple RRs");
+ result = DNS_R_FORMERR;
+ goto done;
+ }
+
+ /* The one rdataset must be an SOA. */
+ if (zone_rdataset->type != dns_rdatatype_soa) {
+ notify_log(client, ISC_LOG_NOTICE,
+ "notify question section contains no SOA");
+ result = DNS_R_FORMERR;
+ goto done;
+ }
+
+ tsigkey = dns_message_gettsigkey(request);
+ if (tsigkey != NULL) {
+ dns_name_format(&tsigkey->name, namebuf, sizeof(namebuf));
+
+ if (tsigkey->generated) {
+ char cnamebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(tsigkey->creator, cnamebuf,
+ sizeof(cnamebuf));
+ snprintf(tsigbuf, sizeof(tsigbuf), ": TSIG '%s' (%s)",
+ namebuf, cnamebuf);
+ } else {
+ snprintf(tsigbuf, sizeof(tsigbuf), ": TSIG '%s'",
+ namebuf);
+ }
+ } else {
+ tsigbuf[0] = '\0';
+ }
+
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ result = dns_zt_find(client->view->zonetable, zonename, 0, NULL, &zone);
+ if (result == ISC_R_SUCCESS) {
+ dns_zonetype_t zonetype = dns_zone_gettype(zone);
+
+ if ((zonetype == dns_zone_primary) ||
+ (zonetype == dns_zone_secondary) ||
+ (zonetype == dns_zone_mirror) ||
+ (zonetype == dns_zone_stub))
+ {
+ isc_sockaddr_t *from = ns_client_getsockaddr(client);
+ isc_sockaddr_t *to = ns_client_getdestaddr(client);
+ notify_log(client, ISC_LOG_INFO,
+ "received notify for zone '%s'%s", namebuf,
+ tsigbuf);
+ result = dns_zone_notifyreceive(zone, from, to,
+ request);
+ goto done;
+ }
+ }
+
+ notify_log(client, ISC_LOG_NOTICE,
+ "received notify for zone '%s'%s: not authoritative",
+ namebuf, tsigbuf);
+ result = DNS_R_NOTAUTH;
+
+done:
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ respond(client, result);
+}
diff --git a/lib/ns/query.c b/lib/ns/query.c
new file mode 100644
index 0000000..00ef955
--- /dev/null
+++ b/lib/ns/query.c
@@ -0,0 +1,12446 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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 <string.h>
+
+#include <isc/hex.h>
+#include <isc/mem.h>
+#include <isc/once.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/serial.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/thread.h>
+#include <isc/util.h>
+
+#include <dns/adb.h>
+#include <dns/badcache.h>
+#include <dns/byaddr.h>
+#include <dns/cache.h>
+#include <dns/db.h>
+#include <dns/dlz.h>
+#include <dns/dns64.h>
+#include <dns/dnsrps.h>
+#include <dns/dnssec.h>
+#include <dns/events.h>
+#include <dns/keytable.h>
+#include <dns/message.h>
+#include <dns/ncache.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/order.h>
+#include <dns/rbt.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/resolver.h>
+#include <dns/result.h>
+#include <dns/stats.h>
+#include <dns/tkey.h>
+#include <dns/types.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include <ns/client.h>
+#include <ns/events.h>
+#include <ns/hooks.h>
+#include <ns/interfacemgr.h>
+#include <ns/log.h>
+#include <ns/server.h>
+#include <ns/sortlist.h>
+#include <ns/stats.h>
+#include <ns/xfrout.h>
+
+#if 0
+/*
+ * It has been recommended that DNS64 be changed to return excluded
+ * AAAA addresses if DNS64 synthesis does not occur. This minimises
+ * the impact on the lookup results. While most DNS AAAA lookups are
+ * done to send IP packets to a host, not all of them are and filtering
+ * excluded addresses has a negative impact on those uses.
+ */
+#define dns64_bis_return_excluded_addresses 1
+#endif /* if 0 */
+
+/*%
+ * Maximum number of chained queries before we give up
+ * to prevent CNAME loops.
+ */
+#define MAX_RESTARTS 16
+
+#define QUERY_ERROR(qctx, r) \
+ do { \
+ (qctx)->result = r; \
+ (qctx)->want_restart = false; \
+ (qctx)->line = __LINE__; \
+ } while (0)
+
+/*% Partial answer? */
+#define PARTIALANSWER(c) \
+ (((c)->query.attributes & NS_QUERYATTR_PARTIALANSWER) != 0)
+/*% Use Cache? */
+#define USECACHE(c) (((c)->query.attributes & NS_QUERYATTR_CACHEOK) != 0)
+/*% Recursion OK? */
+#define RECURSIONOK(c) (((c)->query.attributes & NS_QUERYATTR_RECURSIONOK) != 0)
+/*% Recursing? */
+#define RECURSING(c) (((c)->query.attributes & NS_QUERYATTR_RECURSING) != 0)
+/*% Want Recursion? */
+#define WANTRECURSION(c) \
+ (((c)->query.attributes & NS_QUERYATTR_WANTRECURSION) != 0)
+/*% Is TCP? */
+#define TCP(c) (((c)->attributes & NS_CLIENTATTR_TCP) != 0)
+
+/*% Want DNSSEC? */
+#define WANTDNSSEC(c) (((c)->attributes & NS_CLIENTATTR_WANTDNSSEC) != 0)
+/*% Want WANTAD? */
+#define WANTAD(c) (((c)->attributes & NS_CLIENTATTR_WANTAD) != 0)
+/*% Client presented a valid COOKIE. */
+#define HAVECOOKIE(c) (((c)->attributes & NS_CLIENTATTR_HAVECOOKIE) != 0)
+/*% Client presented a COOKIE. */
+#define WANTCOOKIE(c) (((c)->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0)
+/*% Client presented a CLIENT-SUBNET option. */
+#define HAVEECS(c) (((c)->attributes & NS_CLIENTATTR_HAVEECS) != 0)
+/*% No authority? */
+#define NOAUTHORITY(c) (((c)->query.attributes & NS_QUERYATTR_NOAUTHORITY) != 0)
+/*% No additional? */
+#define NOADDITIONAL(c) \
+ (((c)->query.attributes & NS_QUERYATTR_NOADDITIONAL) != 0)
+/*% Secure? */
+#define SECURE(c) (((c)->query.attributes & NS_QUERYATTR_SECURE) != 0)
+/*% DNS64 A lookup? */
+#define DNS64(c) (((c)->query.attributes & NS_QUERYATTR_DNS64) != 0)
+
+#define DNS64EXCLUDE(c) \
+ (((c)->query.attributes & NS_QUERYATTR_DNS64EXCLUDE) != 0)
+
+#define REDIRECT(c) (((c)->query.attributes & NS_QUERYATTR_REDIRECT) != 0)
+
+/*% Was the client already sent a response? */
+#define QUERY_ANSWERED(q) (((q)->attributes & NS_QUERYATTR_ANSWERED) != 0)
+
+/*% Have we already processed an answer via stale-answer-client-timeout? */
+#define QUERY_STALEPENDING(q) \
+ (((q)->attributes & NS_QUERYATTR_STALEPENDING) != 0)
+
+/*% Does the query allow stale data in the response? */
+#define QUERY_STALEOK(q) (((q)->attributes & NS_QUERYATTR_STALEOK) != 0)
+
+/*% Does the query wants to check for stale RRset due to a timeout? */
+#define QUERY_STALETIMEOUT(q) (((q)->dboptions & DNS_DBFIND_STALETIMEOUT) != 0)
+
+/*% Does the rdataset 'r' have an attached 'No QNAME Proof'? */
+#define NOQNAME(r) (((r)->attributes & DNS_RDATASETATTR_NOQNAME) != 0)
+
+/*% Does the rdataset 'r' contain a stale answer? */
+#define STALE(r) (((r)->attributes & DNS_RDATASETATTR_STALE) != 0)
+
+/*% Does the rdataset 'r' is stale and within stale-refresh-time? */
+#define STALE_WINDOW(r) (((r)->attributes & DNS_RDATASETATTR_STALE_WINDOW) != 0)
+
+#ifdef WANT_QUERYTRACE
+static void
+client_trace(ns_client_t *client, int level, const char *message) {
+ if (client != NULL && client->query.qname != NULL) {
+ if (isc_log_wouldlog(ns_lctx, level)) {
+ char qbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+ dns_name_format(client->query.qname, qbuf,
+ sizeof(qbuf));
+ dns_rdatatype_format(client->query.qtype, tbuf,
+ sizeof(tbuf));
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY, level,
+ "query client=%p thread=0x%" PRIxPTR
+ "(%s/%s): %s",
+ client, isc_thread_self(), qbuf, tbuf,
+ message);
+ }
+ } else {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY, level,
+ "query client=%p thread=0x%" PRIxPTR
+ "(<unknown-query>): %s",
+ client, isc_thread_self(), message);
+ }
+}
+#define CTRACE(l, m) client_trace(client, l, m)
+#define CCTRACE(l, m) client_trace(qctx->client, l, m)
+#else /* ifdef WANT_QUERYTRACE */
+#define CTRACE(l, m) ((void)m)
+#define CCTRACE(l, m) ((void)m)
+#endif /* WANT_QUERYTRACE */
+
+#define DNS_GETDB_NOEXACT 0x01U
+#define DNS_GETDB_NOLOG 0x02U
+#define DNS_GETDB_PARTIAL 0x04U
+#define DNS_GETDB_IGNOREACL 0x08U
+#define DNS_GETDB_STALEFIRST 0X0CU
+
+#define PENDINGOK(x) (((x)&DNS_DBFIND_PENDINGOK) != 0)
+
+#define SFCACHE_CDFLAG 0x1
+
+/*
+ * SAVE and RESTORE have the same semantics as:
+ *
+ * foo_attach(b, &a);
+ * foo_detach(&b);
+ *
+ * without the locking and magic testing.
+ *
+ * We use the names SAVE and RESTORE to show the operation being performed,
+ * even though the two macros are identical.
+ */
+#define SAVE(a, b) \
+ do { \
+ INSIST(a == NULL); \
+ a = b; \
+ b = NULL; \
+ } while (0)
+#define RESTORE(a, b) SAVE(a, b)
+
+static bool
+validate(ns_client_t *client, dns_db_t *db, dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+static void
+query_findclosestnsec3(dns_name_t *qname, dns_db_t *db,
+ dns_dbversion_t *version, ns_client_t *client,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_name_t *fname, bool exact, dns_name_t *found);
+
+static void
+log_queryerror(ns_client_t *client, isc_result_t result, int line, int level);
+
+static void
+rpz_st_clear(ns_client_t *client);
+
+static bool
+rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset);
+
+static void
+log_noexistnodata(void *val, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+static isc_result_t
+query_addanswer(query_ctx_t *qctx);
+
+static isc_result_t
+query_prepare_delegation_response(query_ctx_t *qctx);
+
+/*
+ * Return the hooktable in use with 'qctx', or if there isn't one
+ * set, return the default hooktable.
+ */
+static ns_hooktable_t *
+get_hooktab(query_ctx_t *qctx) {
+ if (qctx == NULL || qctx->view == NULL || qctx->view->hooktable == NULL)
+ {
+ return (ns__hook_table);
+ }
+
+ return (qctx->view->hooktable);
+}
+
+/*
+ * Call the specified hook function in every configured module that implements
+ * that function. If any hook function returns NS_HOOK_RETURN, we
+ * set 'result' and terminate processing by jumping to the 'cleanup' tag.
+ *
+ * (Note that a hook function may set the 'result' to ISC_R_SUCCESS but
+ * still terminate processing within the calling function. That's why this
+ * is a macro instead of a static function; it needs to be able to use
+ * 'goto cleanup' regardless of the return value.)
+ */
+#define CALL_HOOK(_id, _qctx) \
+ do { \
+ isc_result_t _res = result; \
+ ns_hooktable_t *_tab = get_hooktab(_qctx); \
+ ns_hook_t *_hook; \
+ _hook = ISC_LIST_HEAD((*_tab)[_id]); \
+ while (_hook != NULL) { \
+ ns_hook_action_t _func = _hook->action; \
+ void *_data = _hook->action_data; \
+ INSIST(_func != NULL); \
+ switch (_func(_qctx, _data, &_res)) { \
+ case NS_HOOK_CONTINUE: \
+ _hook = ISC_LIST_NEXT(_hook, link); \
+ break; \
+ case NS_HOOK_RETURN: \
+ result = _res; \
+ goto cleanup; \
+ default: \
+ UNREACHABLE(); \
+ } \
+ } \
+ } while (false)
+
+/*
+ * Call the specified hook function in every configured module that
+ * implements that function. All modules are called; hook function return
+ * codes are ignored. This is intended for use with initialization and
+ * destruction calls which *must* run in every configured module.
+ *
+ * (This could be implemented as a static void function, but is left as a
+ * macro for symmetry with CALL_HOOK above.)
+ */
+#define CALL_HOOK_NORETURN(_id, _qctx) \
+ do { \
+ isc_result_t _res; \
+ ns_hooktable_t *_tab = get_hooktab(_qctx); \
+ ns_hook_t *_hook; \
+ _hook = ISC_LIST_HEAD((*_tab)[_id]); \
+ while (_hook != NULL) { \
+ ns_hook_action_t _func = _hook->action; \
+ void *_data = _hook->action_data; \
+ INSIST(_func != NULL); \
+ _func(_qctx, _data, &_res); \
+ _hook = ISC_LIST_NEXT(_hook, link); \
+ } \
+ } while (false)
+
+/*
+ * The functions defined below implement the query logic that previously lived
+ * in the single very complex function query_find(). The query_ctx_t structure
+ * defined in <ns/query.h> maintains state from function to function. The call
+ * flow for the general query processing algorithm is described below:
+ *
+ * 1. Set up query context and other resources for a client
+ * query (query_setup())
+ *
+ * 2. Start the search (ns__query_start())
+ *
+ * 3. Identify authoritative data sources which may have an answer;
+ * search them (query_lookup()). If an answer is found, go to 7.
+ *
+ * 4. If recursion or cache access are allowed, search the cache
+ * (query_lookup() again, using the cache database) to find a better
+ * answer. If an answer is found, go to 7.
+ *
+ * 5. If recursion is allowed, begin recursion (ns_query_recurse()).
+ * Go to 15 to clean up this phase of the query. When recursion
+ * is complete, processing will resume at 6.
+ *
+ * 6. Resume from recursion; set up query context for resumed processing.
+ *
+ * 7. Determine what sort of answer we've found (query_gotanswer())
+ * and call other functions accordingly:
+ * - not found (auth or cache), go to 8
+ * - delegation, go to 9
+ * - no such domain (auth), go to 10
+ * - empty answer (auth), go to 11
+ * - negative response (cache), go to 12
+ * - answer found, go to 13
+ *
+ * 8. The answer was not found in the database (query_notfound().
+ * Set up a referral and go to 9.
+ *
+ * 9. Handle a delegation response (query_delegation()). If we need
+ * to and are allowed to recurse (query_delegation_recurse()), go to 5,
+ * otherwise go to 15 to clean up and return the delegation to the client.
+ *
+ * 10. No such domain (query_nxdomain()). Attempt redirection; if
+ * unsuccessful, add authority section records (query_addsoa(),
+ * query_addauth()), then go to 15 to return NXDOMAIN to client.
+ *
+ * 11. Empty answer (query_nodata()). Add authority section records
+ * (query_addsoa(), query_addauth()) and signatures if authoritative
+ * (query_sign_nodata()) then go to 15 and return
+ * NOERROR/ANCOUNT=0 to client.
+ *
+ * 12. No such domain or empty answer returned from cache (query_ncache()).
+ * Set response code appropriately, go to 11.
+ *
+ * 13. Prepare a response (query_prepresponse()) and then fill it
+ * appropriately (query_respond(), or for type ANY,
+ * query_respond_any()).
+ *
+ * 14. If a restart is needed due to CNAME/DNAME chaining, go to 2.
+ *
+ * 15. Clean up resources. If recursing, stop and wait for the event
+ * handler to be called back (step 6). If an answer is ready,
+ * return it to the client.
+ *
+ * (XXX: This description omits several special cases including
+ * DNS64, RPZ, RRL, and the SERVFAIL cache. It also doesn't discuss
+ * plugins.)
+ */
+
+static void
+query_trace(query_ctx_t *qctx);
+
+static void
+qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype,
+ query_ctx_t *qctx);
+
+static isc_result_t
+query_setup(ns_client_t *client, dns_rdatatype_t qtype);
+
+static isc_result_t
+query_lookup(query_ctx_t *qctx);
+
+static void
+fetch_callback(isc_task_t *task, isc_event_t *event);
+
+static void
+recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype,
+ const dns_name_t *qname, const dns_name_t *qdomain);
+
+static isc_result_t
+query_resume(query_ctx_t *qctx);
+
+static isc_result_t
+query_checkrrl(query_ctx_t *qctx, isc_result_t result);
+
+static isc_result_t
+query_checkrpz(query_ctx_t *qctx, isc_result_t result);
+
+static isc_result_t
+query_rpzcname(query_ctx_t *qctx, dns_name_t *cname);
+
+static isc_result_t
+query_gotanswer(query_ctx_t *qctx, isc_result_t result);
+
+static void
+query_addnoqnameproof(query_ctx_t *qctx);
+
+static isc_result_t
+query_respond_any(query_ctx_t *qctx);
+
+static isc_result_t
+query_respond(query_ctx_t *qctx);
+
+static isc_result_t
+query_dns64(query_ctx_t *qctx);
+
+static void
+query_filter64(query_ctx_t *qctx);
+
+static isc_result_t
+query_notfound(query_ctx_t *qctx);
+
+static isc_result_t
+query_zone_delegation(query_ctx_t *qctx);
+
+static isc_result_t
+query_delegation(query_ctx_t *qctx);
+
+static isc_result_t
+query_delegation_recurse(query_ctx_t *qctx);
+
+static void
+query_addds(query_ctx_t *qctx);
+
+static isc_result_t
+query_nodata(query_ctx_t *qctx, isc_result_t result);
+
+static isc_result_t
+query_sign_nodata(query_ctx_t *qctx);
+
+static void
+query_addnxrrsetnsec(query_ctx_t *qctx);
+
+static isc_result_t
+query_nxdomain(query_ctx_t *qctx, isc_result_t res);
+
+static isc_result_t
+query_redirect(query_ctx_t *qctx);
+
+static isc_result_t
+query_ncache(query_ctx_t *qctx, isc_result_t result);
+
+static isc_result_t
+query_coveringnsec(query_ctx_t *qctx);
+
+static isc_result_t
+query_zerottl_refetch(query_ctx_t *qctx);
+
+static isc_result_t
+query_cname(query_ctx_t *qctx);
+
+static isc_result_t
+query_dname(query_ctx_t *qctx);
+
+static isc_result_t
+query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl);
+
+static isc_result_t
+query_prepresponse(query_ctx_t *qctx);
+
+static isc_result_t
+query_addsoa(query_ctx_t *qctx, unsigned int override_ttl,
+ dns_section_t section);
+
+static isc_result_t
+query_addns(query_ctx_t *qctx);
+
+static void
+query_addbestns(query_ctx_t *qctx);
+
+static void
+query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata);
+
+static void
+query_addauth(query_ctx_t *qctx);
+
+static void
+query_clear_stale(ns_client_t *client);
+
+/*
+ * Increment query statistics counters.
+ */
+static void
+inc_stats(ns_client_t *client, isc_statscounter_t counter) {
+ dns_zone_t *zone = client->query.authzone;
+ dns_rdatatype_t qtype;
+ dns_rdataset_t *rdataset;
+ isc_stats_t *zonestats;
+ dns_stats_t *querystats = NULL;
+
+ ns_stats_increment(client->sctx->nsstats, counter);
+
+ if (zone == NULL) {
+ return;
+ }
+
+ /* Do regular response type stats */
+ zonestats = dns_zone_getrequeststats(zone);
+
+ if (zonestats != NULL) {
+ isc_stats_increment(zonestats, counter);
+ }
+
+ /* Do query type statistics
+ *
+ * We only increment per-type if we're using the authoritative
+ * answer counter, preventing double-counting.
+ */
+ if (counter == ns_statscounter_authans) {
+ querystats = dns_zone_getrcvquerystats(zone);
+ if (querystats != NULL) {
+ rdataset = ISC_LIST_HEAD(client->query.qname->list);
+ if (rdataset != NULL) {
+ qtype = rdataset->type;
+ dns_rdatatypestats_increment(querystats, qtype);
+ }
+ }
+ }
+}
+
+static void
+query_send(ns_client_t *client) {
+ isc_statscounter_t counter;
+
+ if ((client->message->flags & DNS_MESSAGEFLAG_AA) == 0) {
+ inc_stats(client, ns_statscounter_nonauthans);
+ } else {
+ inc_stats(client, ns_statscounter_authans);
+ }
+
+ if (client->message->rcode == dns_rcode_noerror) {
+ dns_section_t answer = DNS_SECTION_ANSWER;
+ if (ISC_LIST_EMPTY(client->message->sections[answer])) {
+ if (client->query.isreferral) {
+ counter = ns_statscounter_referral;
+ } else {
+ counter = ns_statscounter_nxrrset;
+ }
+ } else {
+ counter = ns_statscounter_success;
+ }
+ } else if (client->message->rcode == dns_rcode_nxdomain) {
+ counter = ns_statscounter_nxdomain;
+ } else if (client->message->rcode == dns_rcode_badcookie) {
+ counter = ns_statscounter_badcookie;
+ } else { /* We end up here in case of YXDOMAIN, and maybe others */
+ counter = ns_statscounter_failure;
+ }
+
+ inc_stats(client, counter);
+ ns_client_send(client);
+
+ if (!client->nodetach) {
+ isc_nmhandle_detach(&client->reqhandle);
+ }
+}
+
+static void
+query_error(ns_client_t *client, isc_result_t result, int line) {
+ int loglevel = ISC_LOG_DEBUG(3);
+
+ switch (dns_result_torcode(result)) {
+ case dns_rcode_servfail:
+ loglevel = ISC_LOG_DEBUG(1);
+ inc_stats(client, ns_statscounter_servfail);
+ break;
+ case dns_rcode_formerr:
+ inc_stats(client, ns_statscounter_formerr);
+ break;
+ default:
+ inc_stats(client, ns_statscounter_failure);
+ break;
+ }
+
+ if ((client->sctx->options & NS_SERVER_LOGQUERIES) != 0) {
+ loglevel = ISC_LOG_INFO;
+ }
+
+ log_queryerror(client, result, line, loglevel);
+
+ ns_client_error(client, result);
+
+ if (!client->nodetach) {
+ isc_nmhandle_detach(&client->reqhandle);
+ }
+}
+
+static void
+query_next(ns_client_t *client, isc_result_t result) {
+ if (result == DNS_R_DUPLICATE) {
+ inc_stats(client, ns_statscounter_duplicate);
+ } else if (result == DNS_R_DROP) {
+ inc_stats(client, ns_statscounter_dropped);
+ } else {
+ inc_stats(client, ns_statscounter_failure);
+ }
+ ns_client_drop(client, result);
+
+ if (!client->nodetach) {
+ isc_nmhandle_detach(&client->reqhandle);
+ }
+}
+
+static void
+query_freefreeversions(ns_client_t *client, bool everything) {
+ ns_dbversion_t *dbversion, *dbversion_next;
+ unsigned int i;
+
+ for (dbversion = ISC_LIST_HEAD(client->query.freeversions), i = 0;
+ dbversion != NULL; dbversion = dbversion_next, i++)
+ {
+ dbversion_next = ISC_LIST_NEXT(dbversion, link);
+ /*
+ * If we're not freeing everything, we keep the first three
+ * dbversions structures around.
+ */
+ if (i > 3 || everything) {
+ ISC_LIST_UNLINK(client->query.freeversions, dbversion,
+ link);
+ isc_mem_put(client->mctx, dbversion,
+ sizeof(*dbversion));
+ }
+ }
+}
+
+void
+ns_query_cancel(ns_client_t *client) {
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ LOCK(&client->query.fetchlock);
+ if (client->query.fetch != NULL) {
+ dns_resolver_cancelfetch(client->query.fetch);
+
+ client->query.fetch = NULL;
+ }
+ if (client->query.hookactx != NULL) {
+ client->query.hookactx->cancel(client->query.hookactx);
+ client->query.hookactx = NULL;
+ }
+ UNLOCK(&client->query.fetchlock);
+}
+
+static void
+query_reset(ns_client_t *client, bool everything) {
+ isc_buffer_t *dbuf, *dbuf_next;
+ ns_dbversion_t *dbversion, *dbversion_next;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_reset");
+
+ /*%
+ * Reset the query state of a client to its default state.
+ */
+
+ /*
+ * Cancel the fetch if it's running.
+ */
+ ns_query_cancel(client);
+
+ /*
+ * Cleanup any active versions.
+ */
+ for (dbversion = ISC_LIST_HEAD(client->query.activeversions);
+ dbversion != NULL; dbversion = dbversion_next)
+ {
+ dbversion_next = ISC_LIST_NEXT(dbversion, link);
+ dns_db_closeversion(dbversion->db, &dbversion->version, false);
+ dns_db_detach(&dbversion->db);
+ ISC_LIST_INITANDAPPEND(client->query.freeversions, dbversion,
+ link);
+ }
+ ISC_LIST_INIT(client->query.activeversions);
+
+ if (client->query.authdb != NULL) {
+ dns_db_detach(&client->query.authdb);
+ }
+ if (client->query.authzone != NULL) {
+ dns_zone_detach(&client->query.authzone);
+ }
+
+ if (client->query.dns64_aaaa != NULL) {
+ ns_client_putrdataset(client, &client->query.dns64_aaaa);
+ }
+ if (client->query.dns64_sigaaaa != NULL) {
+ ns_client_putrdataset(client, &client->query.dns64_sigaaaa);
+ }
+ if (client->query.dns64_aaaaok != NULL) {
+ isc_mem_put(client->mctx, client->query.dns64_aaaaok,
+ client->query.dns64_aaaaoklen * sizeof(bool));
+ client->query.dns64_aaaaok = NULL;
+ client->query.dns64_aaaaoklen = 0;
+ }
+
+ ns_client_putrdataset(client, &client->query.redirect.rdataset);
+ ns_client_putrdataset(client, &client->query.redirect.sigrdataset);
+ if (client->query.redirect.db != NULL) {
+ if (client->query.redirect.node != NULL) {
+ dns_db_detachnode(client->query.redirect.db,
+ &client->query.redirect.node);
+ }
+ dns_db_detach(&client->query.redirect.db);
+ }
+ if (client->query.redirect.zone != NULL) {
+ dns_zone_detach(&client->query.redirect.zone);
+ }
+
+ query_freefreeversions(client, everything);
+
+ for (dbuf = ISC_LIST_HEAD(client->query.namebufs); dbuf != NULL;
+ dbuf = dbuf_next)
+ {
+ dbuf_next = ISC_LIST_NEXT(dbuf, link);
+ if (dbuf_next != NULL || everything) {
+ ISC_LIST_UNLINK(client->query.namebufs, dbuf, link);
+ isc_buffer_free(&dbuf);
+ }
+ }
+
+ if (client->query.restarts > 0) {
+ /*
+ * client->query.qname was dynamically allocated.
+ */
+ dns_message_puttempname(client->message, &client->query.qname);
+ }
+ client->query.qname = NULL;
+ client->query.attributes = (NS_QUERYATTR_RECURSIONOK |
+ NS_QUERYATTR_CACHEOK | NS_QUERYATTR_SECURE);
+ client->query.restarts = 0;
+ client->query.timerset = false;
+ if (client->query.rpz_st != NULL) {
+ rpz_st_clear(client);
+ if (everything) {
+ INSIST(client->query.rpz_st->rpsdb == NULL);
+ isc_mem_put(client->mctx, client->query.rpz_st,
+ sizeof(*client->query.rpz_st));
+ client->query.rpz_st = NULL;
+ }
+ }
+ client->query.origqname = NULL;
+ client->query.dboptions = 0;
+ client->query.fetchoptions = 0;
+ client->query.gluedb = NULL;
+ client->query.authdbset = false;
+ client->query.isreferral = false;
+ client->query.dns64_options = 0;
+ client->query.dns64_ttl = UINT32_MAX;
+ recparam_update(&client->query.recparam, 0, NULL, NULL);
+ client->query.root_key_sentinel_keyid = 0;
+ client->query.root_key_sentinel_is_ta = false;
+ client->query.root_key_sentinel_not_ta = false;
+}
+
+static void
+query_cleanup(ns_client_t *client) {
+ query_reset(client, false);
+}
+
+void
+ns_query_free(ns_client_t *client) {
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ query_reset(client, true);
+}
+
+isc_result_t
+ns_query_init(ns_client_t *client) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ ISC_LIST_INIT(client->query.namebufs);
+ ISC_LIST_INIT(client->query.activeversions);
+ ISC_LIST_INIT(client->query.freeversions);
+ client->query.restarts = 0;
+ client->query.timerset = false;
+ client->query.rpz_st = NULL;
+ client->query.qname = NULL;
+ /*
+ * This mutex is destroyed when the client is destroyed in
+ * exit_check().
+ */
+ isc_mutex_init(&client->query.fetchlock);
+
+ client->query.fetch = NULL;
+ client->query.prefetch = NULL;
+ client->query.authdb = NULL;
+ client->query.authzone = NULL;
+ client->query.authdbset = false;
+ client->query.isreferral = false;
+ client->query.dns64_aaaa = NULL;
+ client->query.dns64_sigaaaa = NULL;
+ client->query.dns64_aaaaok = NULL;
+ client->query.dns64_aaaaoklen = 0;
+ client->query.redirect.db = NULL;
+ client->query.redirect.node = NULL;
+ client->query.redirect.zone = NULL;
+ client->query.redirect.qtype = dns_rdatatype_none;
+ client->query.redirect.result = ISC_R_SUCCESS;
+ client->query.redirect.rdataset = NULL;
+ client->query.redirect.sigrdataset = NULL;
+ client->query.redirect.authoritative = false;
+ client->query.redirect.is_zone = false;
+ client->query.redirect.fname =
+ dns_fixedname_initname(&client->query.redirect.fixed);
+ query_reset(client, false);
+ ns_client_newdbversion(client, 3);
+ ns_client_newnamebuf(client);
+
+ return (result);
+}
+
+/*%
+ * Check if 'client' is allowed to query the cache of its associated view.
+ * Unless 'options' has DNS_GETDB_NOLOG set, log the result of cache ACL
+ * evaluation using the appropriate level, along with 'name' and 'qtype'.
+ *
+ * The cache ACL is only evaluated once for each client and then the result is
+ * cached: if NS_QUERYATTR_CACHEACLOKVALID is set in client->query.attributes,
+ * cache ACL evaluation has already been performed. The evaluation result is
+ * also stored in client->query.attributes: if NS_QUERYATTR_CACHEACLOK is set,
+ * the client is allowed cache access.
+ *
+ * Returns:
+ *
+ *\li #ISC_R_SUCCESS 'client' is allowed to access cache
+ *\li #DNS_R_REFUSED 'client' is not allowed to access cache
+ */
+static isc_result_t
+query_checkcacheaccess(ns_client_t *client, const dns_name_t *name,
+ dns_rdatatype_t qtype, unsigned int options) {
+ isc_result_t result;
+
+ if ((client->query.attributes & NS_QUERYATTR_CACHEACLOKVALID) == 0) {
+ enum refusal_reasons {
+ ALLOW_QUERY_CACHE,
+ ALLOW_QUERY_CACHE_ON
+ };
+ static const char *acl_desc[] = {
+ "allow-query-cache did not match",
+ "allow-query-cache-on did not match",
+ };
+
+ /*
+ * The view's cache ACLs have not yet been evaluated.
+ * Do it now. Both allow-query-cache and
+ * allow-query-cache-on must be satisfied.
+ */
+ bool log = ((options & DNS_GETDB_NOLOG) == 0);
+ char msg[NS_CLIENT_ACLMSGSIZE("query (cache)")];
+
+ enum refusal_reasons refusal_reason = ALLOW_QUERY_CACHE;
+ result = ns_client_checkaclsilent(client, NULL,
+ client->view->cacheacl, true);
+ if (result == ISC_R_SUCCESS) {
+ refusal_reason = ALLOW_QUERY_CACHE_ON;
+ result = ns_client_checkaclsilent(
+ client, &client->destaddr,
+ client->view->cacheonacl, true);
+ }
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We were allowed by the "allow-query-cache" ACL.
+ */
+ client->query.attributes |= NS_QUERYATTR_CACHEACLOK;
+ if (log && isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(3)))
+ {
+ ns_client_aclmsg("query (cache)", name, qtype,
+ client->view->rdclass, msg,
+ sizeof(msg));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY,
+ ISC_LOG_DEBUG(3), "%s approved",
+ msg);
+ }
+ } else {
+ /*
+ * We were denied by the "allow-query-cache" ACL.
+ * There is no need to clear NS_QUERYATTR_CACHEACLOK
+ * since it is cleared by query_reset(), before query
+ * processing starts.
+ */
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED,
+ NULL);
+
+ if (log) {
+ ns_client_aclmsg("query (cache)", name, qtype,
+ client->view->rdclass, msg,
+ sizeof(msg));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s denied (%s)", msg,
+ acl_desc[refusal_reason]);
+ }
+ }
+
+ /*
+ * Evaluation has been finished; make sure we will just consult
+ * NS_QUERYATTR_CACHEACLOK for this client from now on.
+ */
+ client->query.attributes |= NS_QUERYATTR_CACHEACLOKVALID;
+ }
+
+ return ((client->query.attributes & NS_QUERYATTR_CACHEACLOK) != 0
+ ? ISC_R_SUCCESS
+ : DNS_R_REFUSED);
+}
+
+static isc_result_t
+query_validatezonedb(ns_client_t *client, const dns_name_t *name,
+ dns_rdatatype_t qtype, unsigned int options,
+ dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t **versionp) {
+ isc_result_t result;
+ dns_acl_t *queryacl, *queryonacl;
+ ns_dbversion_t *dbversion;
+
+ REQUIRE(zone != NULL);
+ REQUIRE(db != NULL);
+
+ /*
+ * Mirror zone data is treated as cache data.
+ */
+ if (dns_zone_gettype(zone) == dns_zone_mirror) {
+ return (query_checkcacheaccess(client, name, qtype, options));
+ }
+
+ /*
+ * This limits our searching to the zone where the first name
+ * (the query target) was looked for. This prevents following
+ * CNAMES or DNAMES into other zones and prevents returning
+ * additional data from other zones. This does not apply if we're
+ * answering a query where recursion is requested and allowed.
+ */
+ if (client->query.rpz_st == NULL &&
+ !(WANTRECURSION(client) && RECURSIONOK(client)) &&
+ client->query.authdbset && db != client->query.authdb)
+ {
+ return (DNS_R_REFUSED);
+ }
+
+ /*
+ * Non recursive query to a static-stub zone is prohibited; its
+ * zone content is not public data, but a part of local configuration
+ * and should not be disclosed.
+ */
+ if (dns_zone_gettype(zone) == dns_zone_staticstub &&
+ !RECURSIONOK(client))
+ {
+ return (DNS_R_REFUSED);
+ }
+
+ /*
+ * If the zone has an ACL, we'll check it, otherwise
+ * we use the view's "allow-query" ACL. Each ACL is only checked
+ * once per query.
+ *
+ * Also, get the database version to use.
+ */
+
+ /*
+ * Get the current version of this database.
+ */
+ dbversion = ns_client_findversion(client, db);
+ if (dbversion == NULL) {
+ CTRACE(ISC_LOG_ERROR, "unable to get db version");
+ return (DNS_R_SERVFAIL);
+ }
+
+ if ((options & DNS_GETDB_IGNOREACL) != 0) {
+ goto approved;
+ }
+ if (dbversion->acl_checked) {
+ if (!dbversion->queryok) {
+ return (DNS_R_REFUSED);
+ }
+ goto approved;
+ }
+
+ queryacl = dns_zone_getqueryacl(zone);
+ if (queryacl == NULL) {
+ queryacl = client->view->queryacl;
+ if ((client->query.attributes & NS_QUERYATTR_QUERYOKVALID) != 0)
+ {
+ /*
+ * We've evaluated the view's queryacl already. If
+ * NS_QUERYATTR_QUERYOK is set, then the client is
+ * allowed to make queries, otherwise the query should
+ * be refused.
+ */
+ dbversion->acl_checked = true;
+ if ((client->query.attributes & NS_QUERYATTR_QUERYOK) ==
+ 0)
+ {
+ dbversion->queryok = false;
+ return (DNS_R_REFUSED);
+ }
+ dbversion->queryok = true;
+ goto approved;
+ }
+ }
+
+ result = ns_client_checkaclsilent(client, NULL, queryacl, true);
+ if ((options & DNS_GETDB_NOLOG) == 0) {
+ char msg[NS_CLIENT_ACLMSGSIZE("query")];
+ if (result == ISC_R_SUCCESS) {
+ if (isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(3))) {
+ ns_client_aclmsg("query", name, qtype,
+ client->view->rdclass, msg,
+ sizeof(msg));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY,
+ ISC_LOG_DEBUG(3), "%s approved",
+ msg);
+ }
+ } else {
+ ns_client_aclmsg("query", name, qtype,
+ client->view->rdclass, msg,
+ sizeof(msg));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s denied", msg);
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED,
+ NULL);
+ }
+ }
+
+ if (queryacl == client->view->queryacl) {
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We were allowed by the default
+ * "allow-query" ACL. Remember this so we
+ * don't have to check again.
+ */
+ client->query.attributes |= NS_QUERYATTR_QUERYOK;
+ }
+ /*
+ * We've now evaluated the view's query ACL, and
+ * the NS_QUERYATTR_QUERYOK attribute is now valid.
+ */
+ client->query.attributes |= NS_QUERYATTR_QUERYOKVALID;
+ }
+
+ /* If and only if we've gotten this far, check allow-query-on too */
+ if (result == ISC_R_SUCCESS) {
+ queryonacl = dns_zone_getqueryonacl(zone);
+ if (queryonacl == NULL) {
+ queryonacl = client->view->queryonacl;
+ }
+
+ result = ns_client_checkaclsilent(client, &client->destaddr,
+ queryonacl, true);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED,
+ NULL);
+ }
+ if ((options & DNS_GETDB_NOLOG) == 0 && result != ISC_R_SUCCESS)
+ {
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "query-on denied");
+ }
+ }
+
+ dbversion->acl_checked = true;
+ if (result != ISC_R_SUCCESS) {
+ dbversion->queryok = false;
+ return (DNS_R_REFUSED);
+ }
+ dbversion->queryok = true;
+
+approved:
+ /* Transfer ownership, if necessary. */
+ if (versionp != NULL) {
+ *versionp = dbversion->version;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+query_getzonedb(ns_client_t *client, const dns_name_t *name,
+ dns_rdatatype_t qtype, unsigned int options, dns_zone_t **zonep,
+ dns_db_t **dbp, dns_dbversion_t **versionp) {
+ isc_result_t result;
+ unsigned int ztoptions;
+ dns_zone_t *zone = NULL;
+ dns_db_t *db = NULL;
+ bool partial = false;
+
+ REQUIRE(zonep != NULL && *zonep == NULL);
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ /*%
+ * Find a zone database to answer the query.
+ */
+ ztoptions = DNS_ZTFIND_MIRROR;
+ if ((options & DNS_GETDB_NOEXACT) != 0) {
+ ztoptions |= DNS_ZTFIND_NOEXACT;
+ }
+
+ result = dns_zt_find(client->view->zonetable, name, ztoptions, NULL,
+ &zone);
+
+ if (result == DNS_R_PARTIALMATCH) {
+ partial = true;
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
+ result = dns_zone_getdb(zone, &db);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ result = query_validatezonedb(client, name, qtype, options, zone, db,
+ versionp);
+
+ if (result != ISC_R_SUCCESS) {
+ goto fail;
+ }
+
+ /* Transfer ownership. */
+ *zonep = zone;
+ *dbp = db;
+
+ if (partial && (options & DNS_GETDB_PARTIAL) != 0) {
+ return (DNS_R_PARTIALMATCH);
+ }
+ return (ISC_R_SUCCESS);
+
+fail:
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ return (result);
+}
+
+static void
+rpz_log_rewrite(ns_client_t *client, bool disabled, dns_rpz_policy_t policy,
+ dns_rpz_type_t type, dns_zone_t *p_zone, dns_name_t *p_name,
+ dns_name_t *cname, dns_rpz_num_t rpz_num) {
+ char cname_buf[DNS_NAME_FORMATSIZE] = { 0 };
+ char p_name_buf[DNS_NAME_FORMATSIZE];
+ char qname_buf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ const char *s1 = cname_buf, *s2 = cname_buf;
+ dns_rdataset_t *rdataset;
+ dns_rpz_st_t *st;
+ isc_stats_t *zonestats;
+
+ /*
+ * Count enabled rewrites in the global counter.
+ * Count both enabled and disabled rewrites for each zone.
+ */
+ if (!disabled && policy != DNS_RPZ_POLICY_PASSTHRU) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_rpz_rewrites);
+ }
+ if (p_zone != NULL) {
+ zonestats = dns_zone_getrequeststats(p_zone);
+ if (zonestats != NULL) {
+ isc_stats_increment(zonestats,
+ ns_statscounter_rpz_rewrites);
+ }
+ }
+
+ if (!isc_log_wouldlog(ns_lctx, DNS_RPZ_INFO_LEVEL)) {
+ return;
+ }
+
+ st = client->query.rpz_st;
+ if ((st->popt.no_log & DNS_RPZ_ZBIT(rpz_num)) != 0) {
+ return;
+ }
+
+ dns_name_format(client->query.qname, qname_buf, sizeof(qname_buf));
+ dns_name_format(p_name, p_name_buf, sizeof(p_name_buf));
+ if (cname != NULL) {
+ s1 = " (CNAME to: ";
+ dns_name_format(cname, cname_buf, sizeof(cname_buf));
+ s2 = ")";
+ }
+
+ /*
+ * Log Qclass and Qtype in addition to existing
+ * fields.
+ */
+ rdataset = ISC_LIST_HEAD(client->query.origqname->list);
+ INSIST(rdataset != NULL);
+ dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+
+ /* It's possible to have a separate log channel for rpz passthru. */
+ isc_logcategory_t *log_cat = (policy == DNS_RPZ_POLICY_PASSTHRU)
+ ? DNS_LOGCATEGORY_RPZ_PASSTHRU
+ : DNS_LOGCATEGORY_RPZ;
+
+ ns_client_log(client, log_cat, NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL,
+ "%srpz %s %s rewrite %s/%s/%s via %s%s%s%s",
+ disabled ? "disabled " : "", dns_rpz_type2str(type),
+ dns_rpz_policy2str(policy), qname_buf, typebuf, classbuf,
+ p_name_buf, s1, cname_buf, s2);
+}
+
+static void
+rpz_log_fail_helper(ns_client_t *client, int level, dns_name_t *p_name,
+ dns_rpz_type_t rpz_type1, dns_rpz_type_t rpz_type2,
+ const char *str, isc_result_t result) {
+ char qnamebuf[DNS_NAME_FORMATSIZE];
+ char p_namebuf[DNS_NAME_FORMATSIZE];
+ const char *failed, *via, *slash, *str_blank;
+ const char *rpztypestr1;
+ const char *rpztypestr2;
+
+ if (!isc_log_wouldlog(ns_lctx, level)) {
+ return;
+ }
+
+ /*
+ * bin/tests/system/rpz/tests.sh looks for "rpz.*failed" for problems.
+ */
+ if (level <= DNS_RPZ_DEBUG_LEVEL1) {
+ failed = " failed: ";
+ } else {
+ failed = ": ";
+ }
+
+ rpztypestr1 = dns_rpz_type2str(rpz_type1);
+ if (rpz_type2 != DNS_RPZ_TYPE_BAD) {
+ slash = "/";
+ rpztypestr2 = dns_rpz_type2str(rpz_type2);
+ } else {
+ slash = "";
+ rpztypestr2 = "";
+ }
+
+ str_blank = (*str != ' ' && *str != '\0') ? " " : "";
+
+ dns_name_format(client->query.qname, qnamebuf, sizeof(qnamebuf));
+
+ if (p_name != NULL) {
+ via = " via ";
+ dns_name_format(p_name, p_namebuf, sizeof(p_namebuf));
+ } else {
+ via = "";
+ p_namebuf[0] = '\0';
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_QUERY_ERRORS, NS_LOGMODULE_QUERY,
+ level, "rpz %s%s%s rewrite %s%s%s%s%s%s%s", rpztypestr1,
+ slash, rpztypestr2, qnamebuf, via, p_namebuf, str_blank,
+ str, failed, isc_result_totext(result));
+}
+
+static void
+rpz_log_fail(ns_client_t *client, int level, dns_name_t *p_name,
+ dns_rpz_type_t rpz_type, const char *str, isc_result_t result) {
+ rpz_log_fail_helper(client, level, p_name, rpz_type, DNS_RPZ_TYPE_BAD,
+ str, result);
+}
+
+/*
+ * Get a policy rewrite zone database.
+ */
+static isc_result_t
+rpz_getdb(ns_client_t *client, dns_name_t *p_name, dns_rpz_type_t rpz_type,
+ dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp) {
+ char qnamebuf[DNS_NAME_FORMATSIZE];
+ char p_namebuf[DNS_NAME_FORMATSIZE];
+ dns_dbversion_t *rpz_version = NULL;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_getdb");
+
+ result = query_getzonedb(client, p_name, dns_rdatatype_any,
+ DNS_GETDB_IGNOREACL, zonep, dbp, &rpz_version);
+ if (result == ISC_R_SUCCESS) {
+ dns_rpz_st_t *st = client->query.rpz_st;
+
+ /*
+ * It isn't meaningful to log this message when
+ * logging is disabled for some policy zones.
+ */
+ if (st->popt.no_log == 0 &&
+ isc_log_wouldlog(ns_lctx, DNS_RPZ_DEBUG_LEVEL2))
+ {
+ dns_name_format(client->query.qname, qnamebuf,
+ sizeof(qnamebuf));
+ dns_name_format(p_name, p_namebuf, sizeof(p_namebuf));
+ ns_client_log(client, DNS_LOGCATEGORY_RPZ,
+ NS_LOGMODULE_QUERY, DNS_RPZ_DEBUG_LEVEL2,
+ "try rpz %s rewrite %s via %s",
+ dns_rpz_type2str(rpz_type), qnamebuf,
+ p_namebuf);
+ }
+ *versionp = rpz_version;
+ return (ISC_R_SUCCESS);
+ }
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type,
+ "query_getzonedb()", result);
+ return (result);
+}
+
+/*%
+ * Find a cache database to answer the query. This may fail with DNS_R_REFUSED
+ * if the client is not allowed to use the cache.
+ */
+static isc_result_t
+query_getcachedb(ns_client_t *client, const dns_name_t *name,
+ dns_rdatatype_t qtype, dns_db_t **dbp, unsigned int options) {
+ isc_result_t result;
+ dns_db_t *db = NULL;
+
+ REQUIRE(dbp != NULL && *dbp == NULL);
+
+ if (!USECACHE(client)) {
+ return (DNS_R_REFUSED);
+ }
+
+ dns_db_attach(client->view->cachedb, &db);
+
+ result = query_checkcacheaccess(client, name, qtype, options);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detach(&db);
+ }
+
+ /*
+ * If query_checkcacheaccess() succeeded, transfer ownership of 'db'.
+ * Otherwise, 'db' will be NULL due to the dns_db_detach() call above.
+ */
+ *dbp = db;
+
+ return (result);
+}
+
+static isc_result_t
+query_getdb(ns_client_t *client, dns_name_t *name, dns_rdatatype_t qtype,
+ unsigned int options, dns_zone_t **zonep, dns_db_t **dbp,
+ dns_dbversion_t **versionp, bool *is_zonep) {
+ isc_result_t result;
+ isc_result_t tresult;
+ unsigned int namelabels;
+ unsigned int zonelabels;
+ dns_zone_t *zone = NULL;
+
+ REQUIRE(zonep != NULL && *zonep == NULL);
+
+ /* Calculate how many labels are in name. */
+ namelabels = dns_name_countlabels(name);
+ zonelabels = 0;
+
+ /* Try to find name in bind's standard database. */
+ result = query_getzonedb(client, name, qtype, options, &zone, dbp,
+ versionp);
+
+ /* See how many labels are in the zone's name. */
+ if (result == ISC_R_SUCCESS && zone != NULL) {
+ zonelabels = dns_name_countlabels(dns_zone_getorigin(zone));
+ }
+
+ /*
+ * If # zone labels < # name labels, try to find an even better match
+ * Only try if DLZ drivers are loaded for this view
+ */
+ if (zonelabels < namelabels &&
+ !ISC_LIST_EMPTY(client->view->dlz_searched))
+ {
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_db_t *tdbp;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+ dns_clientinfo_setecs(&ci, &client->ecs);
+
+ tdbp = NULL;
+ tresult = dns_view_searchdlz(client->view, name, zonelabels,
+ &cm, &ci, &tdbp);
+ /* If we successful, we found a better match. */
+ if (tresult == ISC_R_SUCCESS) {
+ ns_dbversion_t *dbversion;
+
+ /*
+ * If the previous search returned a zone, detach it.
+ */
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ /*
+ * If the previous search returned a database,
+ * detach it.
+ */
+ if (*dbp != NULL) {
+ dns_db_detach(dbp);
+ }
+
+ /*
+ * If the previous search returned a version, clear it.
+ */
+ *versionp = NULL;
+
+ dbversion = ns_client_findversion(client, tdbp);
+ if (dbversion == NULL) {
+ tresult = ISC_R_NOMEMORY;
+ } else {
+ /*
+ * Be sure to return our database.
+ */
+ *dbp = tdbp;
+ *versionp = dbversion->version;
+ }
+
+ /*
+ * We return a null zone, No stats for DLZ zones.
+ */
+ zone = NULL;
+ result = tresult;
+ }
+ }
+
+ /* If successful, Transfer ownership of zone. */
+ if (result == ISC_R_SUCCESS) {
+ *zonep = zone;
+ /*
+ * If neither attempt above succeeded, return the cache instead
+ */
+ *is_zonep = true;
+ } else {
+ if (result == ISC_R_NOTFOUND) {
+ result = query_getcachedb(client, name, qtype, dbp,
+ options);
+ }
+ *is_zonep = false;
+ }
+ return (result);
+}
+
+static bool
+query_isduplicate(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
+ dns_name_t **mnamep) {
+ dns_section_t section;
+ dns_name_t *mname = NULL;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate");
+
+ for (section = DNS_SECTION_ANSWER; section <= DNS_SECTION_ADDITIONAL;
+ section++)
+ {
+ result = dns_message_findname(client->message, section, name,
+ type, 0, &mname, NULL);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We've already got this RRset in the response.
+ */
+ CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate: true: "
+ "done");
+ return (true);
+ } else if (result == DNS_R_NXRRSET) {
+ /*
+ * The name exists, but the rdataset does not.
+ */
+ if (section == DNS_SECTION_ADDITIONAL) {
+ break;
+ }
+ } else {
+ RUNTIME_CHECK(result == DNS_R_NXDOMAIN);
+ }
+ mname = NULL;
+ }
+
+ if (mnamep != NULL) {
+ *mnamep = mname;
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_isduplicate: false: done");
+ return (false);
+}
+
+/*
+ * Look up data for given 'name' and 'type' in given 'version' of 'db' for
+ * 'client'. Called from query_additionalauth().
+ *
+ * If the lookup is successful:
+ *
+ * - store the node containing the result at 'nodep',
+ *
+ * - store the owner name of the returned node in 'fname',
+ *
+ * - if 'type' is not ANY, dns_db_findext() will put the exact rdataset being
+ * looked for in 'rdataset' and its signatures (if any) in 'sigrdataset',
+ *
+ * - if 'type' is ANY, dns_db_findext() will leave 'rdataset' and
+ * 'sigrdataset' disassociated and the returned node will be iterated in
+ * query_additional_cb().
+ *
+ * If the lookup is not successful:
+ *
+ * - 'nodep' will not be written to,
+ * - 'fname' may still be modified as it is passed to dns_db_findext(),
+ * - 'rdataset' and 'sigrdataset' will remain disassociated.
+ */
+static isc_result_t
+query_additionalauthfind(dns_db_t *db, dns_dbversion_t *version,
+ const dns_name_t *name, dns_rdatatype_t type,
+ ns_client_t *client, dns_dbnode_t **nodep,
+ dns_name_t *fname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_clientinfomethods_t cm;
+ dns_dbnode_t *node = NULL;
+ dns_clientinfo_t ci;
+ isc_result_t result;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Since we are looking for authoritative data, we do not set
+ * the GLUEOK flag. Glue will be looked for later, but not
+ * necessarily in the same database.
+ */
+ result = dns_db_findext(db, name, version, type,
+ client->query.dboptions, client->now, &node,
+ fname, &cm, &ci, rdataset, sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ 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);
+ }
+
+ return (result);
+ }
+
+ /*
+ * Do not return signatures if the zone is not fully signed.
+ */
+ if (sigrdataset != NULL && !dns_db_issecure(db) &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+
+ *nodep = node;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * For query context 'qctx', try finding authoritative additional data for
+ * given 'name' and 'type'. Called from query_additional_cb().
+ *
+ * If successful:
+ *
+ * - store pointers to the database and node which contain the result in
+ * 'dbp' and 'nodep', respectively,
+ *
+ * - store the owner name of the returned node in 'fname',
+ *
+ * - potentially bind 'rdataset' and 'sigrdataset', as explained in the
+ * comment for query_additionalauthfind().
+ *
+ * If unsuccessful:
+ *
+ * - 'dbp' and 'nodep' will not be written to,
+ * - 'fname' may still be modified as it is passed to dns_db_findext(),
+ * - 'rdataset' and 'sigrdataset' will remain disassociated.
+ */
+static isc_result_t
+query_additionalauth(query_ctx_t *qctx, const dns_name_t *name,
+ dns_rdatatype_t type, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_name_t *fname, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ ns_client_t *client = qctx->client;
+ ns_dbversion_t *dbversion = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_zone_t *zone = NULL;
+ dns_db_t *db = NULL;
+ isc_result_t result;
+
+ /*
+ * First, look within the same zone database for authoritative
+ * additional data.
+ */
+ if (!client->query.authdbset || client->query.authdb == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dbversion = ns_client_findversion(client, client->query.authdb);
+ if (dbversion == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dns_db_attach(client->query.authdb, &db);
+ version = dbversion->version;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_additionalauth: same zone");
+
+ result = query_additionalauthfind(db, version, name, type, client,
+ &node, fname, rdataset, sigrdataset);
+ if (result != ISC_R_SUCCESS &&
+ qctx->view->minimalresponses == dns_minimal_no &&
+ RECURSIONOK(client))
+ {
+ /*
+ * If we aren't doing response minimization and recursion is
+ * allowed, we can try and see if any other zone matches.
+ */
+ version = NULL;
+ dns_db_detach(&db);
+ result = query_getzonedb(client, name, type, DNS_GETDB_NOLOG,
+ &zone, &db, &version);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ dns_zone_detach(&zone);
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_additionalauth: other zone");
+
+ result = query_additionalauthfind(db, version, name, type,
+ client, &node, fname,
+ rdataset, sigrdataset);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detach(&db);
+ } else {
+ *nodep = node;
+ node = NULL;
+
+ *dbp = db;
+ db = NULL;
+ }
+
+ return (result);
+}
+
+static isc_result_t
+query_additional_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype,
+ dns_rdataset_t *found) {
+ query_ctx_t *qctx = arg;
+ ns_client_t *client = qctx->client;
+ isc_result_t result, eresult = ISC_R_SUCCESS;
+ dns_dbnode_t *node = NULL;
+ dns_db_t *db = NULL;
+ dns_name_t *fname = NULL, *mname = NULL;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t *trdataset = NULL;
+ isc_buffer_t *dbuf = NULL;
+ isc_buffer_t b;
+ ns_dbversion_t *dbversion = NULL;
+ dns_dbversion_t *version = NULL;
+ bool added_something = false, need_addname = false;
+ dns_rdatatype_t type;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_rdatasetadditional_t additionaltype =
+ dns_rdatasetadditional_fromauth;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(qtype != dns_rdatatype_any);
+
+ if (!WANTDNSSEC(client) && dns_rdatatype_isdnssec(qtype)) {
+ return (ISC_R_SUCCESS);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb");
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * We treat type A additional section processing as if it
+ * were "any address type" additional section processing.
+ * To avoid multiple lookups, we do an 'any' database
+ * lookup and iterate over the node.
+ */
+ if (qtype == dns_rdatatype_a) {
+ type = dns_rdatatype_any;
+ } else {
+ type = qtype;
+ }
+
+ /*
+ * Get some resources.
+ */
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ rdataset = ns_client_newrdataset(client);
+ if (fname == NULL || rdataset == NULL) {
+ goto cleanup;
+ }
+ if (WANTDNSSEC(client)) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ goto cleanup;
+ }
+ }
+
+ /*
+ * If we want only minimal responses and are here, then it must
+ * be for glue.
+ */
+ if (qctx->view->minimalresponses == dns_minimal_yes &&
+ client->query.qtype != dns_rdatatype_ns)
+ {
+ goto try_glue;
+ }
+
+ /*
+ * First, look for authoritative additional data.
+ */
+ result = query_additionalauth(qctx, name, type, &db, &node, fname,
+ rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ goto found;
+ }
+
+ /*
+ * No authoritative data was found. The cache is our next best bet.
+ */
+ if (!qctx->view->recursion) {
+ goto try_glue;
+ }
+
+ additionaltype = dns_rdatasetadditional_fromcache;
+ result = query_getcachedb(client, name, qtype, &db, DNS_GETDB_NOLOG);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Most likely the client isn't allowed to query the cache.
+ */
+ goto try_glue;
+ }
+ /*
+ * Attempt to validate glue.
+ */
+ if (sigrdataset == NULL) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ goto cleanup;
+ }
+ }
+
+ version = NULL;
+ result = dns_db_findext(db, name, version, type,
+ client->query.dboptions | DNS_DBFIND_GLUEOK |
+ DNS_DBFIND_ADDITIONALOK,
+ client->now, &node, fname, &cm, &ci, rdataset,
+ sigrdataset);
+
+ dns_cache_updatestats(qctx->view->cache, result);
+ if (!WANTDNSSEC(client)) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (result == ISC_R_SUCCESS) {
+ goto found;
+ }
+
+ 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);
+ }
+ dns_db_detach(&db);
+
+try_glue:
+ /*
+ * No cached data was found. Glue is our last chance.
+ * RFC1035 sayeth:
+ *
+ * NS records cause both the usual additional section
+ * processing to locate a type A record, and, when used
+ * in a referral, a special search of the zone in which
+ * they reside for glue information.
+ *
+ * This is the "special search". Note that we must search
+ * the zone where the NS record resides, not the zone it
+ * points to, and that we only do the search in the delegation
+ * case (identified by client->query.gluedb being set).
+ */
+
+ if (client->query.gluedb == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * Don't poison caches using the bailiwick protection model.
+ */
+ if (!dns_name_issubdomain(name, dns_db_origin(client->query.gluedb))) {
+ goto cleanup;
+ }
+
+ dbversion = ns_client_findversion(client, client->query.gluedb);
+ if (dbversion == NULL) {
+ goto cleanup;
+ }
+
+ dns_db_attach(client->query.gluedb, &db);
+ version = dbversion->version;
+ additionaltype = dns_rdatasetadditional_fromglue;
+ result = dns_db_findext(db, name, version, type,
+ client->query.dboptions | DNS_DBFIND_GLUEOK,
+ client->now, &node, fname, &cm, &ci, rdataset,
+ sigrdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_ZONECUT &&
+ result != DNS_R_GLUE)
+ {
+ goto cleanup;
+ }
+
+found:
+ /*
+ * We have found a potential additional data rdataset, or
+ * at least a node to iterate over.
+ */
+ ns_client_keepname(client, fname, dbuf);
+
+ /*
+ * Does the caller want the found rdataset?
+ */
+ if (found != NULL && dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_clone(rdataset, found);
+ }
+
+ /*
+ * If we have an rdataset, add it to the additional data
+ * section.
+ */
+ mname = NULL;
+ if (dns_rdataset_isassociated(rdataset) &&
+ !query_isduplicate(client, fname, type, &mname))
+ {
+ if (mname != NULL) {
+ INSIST(mname != fname);
+ ns_client_releasename(client, &fname);
+ fname = mname;
+ } else {
+ need_addname = true;
+ }
+ ISC_LIST_APPEND(fname->list, rdataset, link);
+ trdataset = rdataset;
+ rdataset = NULL;
+ added_something = true;
+ /*
+ * Note: we only add SIGs if we've added the type they cover,
+ * so we do not need to check if the SIG rdataset is already
+ * in the response.
+ */
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ ISC_LIST_APPEND(fname->list, sigrdataset, link);
+ sigrdataset = NULL;
+ }
+ }
+
+ if (qtype == dns_rdatatype_a) {
+ /*
+ * We now go looking for A and AAAA records, along with
+ * their signatures.
+ *
+ * XXXRTH This code could be more efficient.
+ */
+ if (rdataset != NULL) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ } else {
+ rdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL) {
+ goto addname;
+ }
+ }
+ if (sigrdataset != NULL) {
+ if (dns_rdataset_isassociated(sigrdataset)) {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (WANTDNSSEC(client)) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ goto addname;
+ }
+ }
+ if (query_isduplicate(client, fname, dns_rdatatype_a, NULL)) {
+ goto aaaa_lookup;
+ }
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_a,
+ 0, client->now, rdataset,
+ sigrdataset);
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ goto addname;
+ } else if (result == DNS_R_NCACHENXRRSET) {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (result == ISC_R_SUCCESS) {
+ bool invalid = false;
+ mname = NULL;
+ if (additionaltype ==
+ dns_rdatasetadditional_fromcache &&
+ (DNS_TRUST_PENDING(rdataset->trust) ||
+ DNS_TRUST_GLUE(rdataset->trust)))
+ {
+ /* validate() may change rdataset->trust */
+ invalid = !validate(client, db, fname, rdataset,
+ sigrdataset);
+ }
+ if (invalid && DNS_TRUST_PENDING(rdataset->trust)) {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (!query_isduplicate(client, fname,
+ dns_rdatatype_a, &mname))
+ {
+ if (mname != fname) {
+ if (mname != NULL) {
+ ns_client_releasename(client,
+ &fname);
+ fname = mname;
+ } else {
+ need_addname = true;
+ }
+ }
+ ISC_LIST_APPEND(fname->list, rdataset, link);
+ added_something = true;
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ ISC_LIST_APPEND(fname->list,
+ sigrdataset, link);
+ sigrdataset =
+ ns_client_newrdataset(client);
+ }
+ rdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL) {
+ goto addname;
+ }
+ if (WANTDNSSEC(client) && sigrdataset == NULL) {
+ goto addname;
+ }
+ } else {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ }
+ }
+ aaaa_lookup:
+ if (query_isduplicate(client, fname, dns_rdatatype_aaaa, NULL))
+ {
+ goto addname;
+ }
+ result = dns_db_findrdataset(db, node, version,
+ dns_rdatatype_aaaa, 0, client->now,
+ rdataset, sigrdataset);
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ goto addname;
+ } else if (result == DNS_R_NCACHENXRRSET) {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (result == ISC_R_SUCCESS) {
+ bool invalid = false;
+ mname = NULL;
+
+ if (additionaltype ==
+ dns_rdatasetadditional_fromcache &&
+ (DNS_TRUST_PENDING(rdataset->trust) ||
+ DNS_TRUST_GLUE(rdataset->trust)))
+ {
+ /* validate() may change rdataset->trust */
+ invalid = !validate(client, db, fname, rdataset,
+ sigrdataset);
+ }
+
+ if (invalid && DNS_TRUST_PENDING(rdataset->trust)) {
+ dns_rdataset_disassociate(rdataset);
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ } else if (!query_isduplicate(client, fname,
+ dns_rdatatype_aaaa,
+ &mname))
+ {
+ if (mname != fname) {
+ if (mname != NULL) {
+ ns_client_releasename(client,
+ &fname);
+ fname = mname;
+ } else {
+ need_addname = true;
+ }
+ }
+ ISC_LIST_APPEND(fname->list, rdataset, link);
+ added_something = true;
+ if (sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ ISC_LIST_APPEND(fname->list,
+ sigrdataset, link);
+ sigrdataset = NULL;
+ }
+ rdataset = NULL;
+ }
+ }
+ }
+
+addname:
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: addname");
+ /*
+ * If we haven't added anything, then we're done.
+ */
+ if (!added_something) {
+ goto cleanup;
+ }
+
+ /*
+ * We may have added our rdatasets to an existing name, if so, then
+ * need_addname will be false. Whether we used an existing name
+ * or a new one, we must set fname to NULL to prevent cleanup.
+ */
+ if (need_addname) {
+ dns_message_addname(client->message, fname,
+ DNS_SECTION_ADDITIONAL);
+ }
+
+ /*
+ * In some cases, a record that has been added as additional
+ * data may *also* trigger the addition of additional data.
+ * This cannot go more than MAX_RESTARTS levels deep.
+ */
+ if (trdataset != NULL && dns_rdatatype_followadditional(type)) {
+ if (client->additionaldepth++ < MAX_RESTARTS) {
+ eresult = dns_rdataset_additionaldata(
+ trdataset, fname, query_additional_cb, qctx);
+ }
+ client->additionaldepth--;
+ }
+
+ /*
+ * Don't release fname.
+ */
+ fname = NULL;
+
+cleanup:
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: cleanup");
+ ns_client_putrdataset(client, &rdataset);
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: done");
+ return (eresult);
+}
+
+/*
+ * Add 'rdataset' to 'name'.
+ */
+static void
+query_addtoname(dns_name_t *name, dns_rdataset_t *rdataset) {
+ ISC_LIST_APPEND(name->list, rdataset, link);
+}
+
+/*
+ * Set the ordering for 'rdataset'.
+ */
+static void
+query_setorder(query_ctx_t *qctx, dns_name_t *name, dns_rdataset_t *rdataset) {
+ ns_client_t *client = qctx->client;
+ dns_order_t *order = client->view->order;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_setorder");
+
+ UNUSED(client);
+
+ if (order != NULL) {
+ rdataset->attributes |= dns_order_find(
+ order, name, rdataset->type, rdataset->rdclass);
+ }
+ rdataset->attributes |= DNS_RDATASETATTR_LOADORDER;
+}
+
+/*
+ * Handle glue and fetch any other needed additional data for 'rdataset'.
+ */
+static void
+query_additional(query_ctx_t *qctx, dns_name_t *name,
+ dns_rdataset_t *rdataset) {
+ ns_client_t *client = qctx->client;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional");
+
+ if (NOADDITIONAL(client)) {
+ return;
+ }
+
+ /*
+ * Try to process glue directly.
+ */
+ if (qctx->view->use_glue_cache &&
+ (rdataset->type == dns_rdatatype_ns) &&
+ (client->query.gluedb != NULL) &&
+ dns_db_iszone(client->query.gluedb))
+ {
+ ns_dbversion_t *dbversion;
+
+ dbversion = ns_client_findversion(client, client->query.gluedb);
+ if (dbversion == NULL) {
+ goto regular;
+ }
+
+ result = dns_rdataset_addglue(rdataset, dbversion->version,
+ client->message);
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+ }
+
+regular:
+ /*
+ * Add other additional data if needed.
+ * We don't care if dns_rdataset_additionaldata() fails.
+ */
+ (void)dns_rdataset_additionaldata(rdataset, name, query_additional_cb,
+ qctx);
+ CTRACE(ISC_LOG_DEBUG(3), "query_additional: done");
+}
+
+static void
+query_addrrset(query_ctx_t *qctx, dns_name_t **namep,
+ dns_rdataset_t **rdatasetp, dns_rdataset_t **sigrdatasetp,
+ isc_buffer_t *dbuf, dns_section_t section) {
+ isc_result_t result;
+ ns_client_t *client = qctx->client;
+ dns_name_t *name = *namep, *mname = NULL;
+ dns_rdataset_t *rdataset = *rdatasetp, *mrdataset = NULL;
+ dns_rdataset_t *sigrdataset = NULL;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addrrset");
+
+ REQUIRE(name != NULL);
+
+ if (sigrdatasetp != NULL) {
+ sigrdataset = *sigrdatasetp;
+ }
+
+ /*%
+ * To the current response for 'client', add the answer RRset
+ * '*rdatasetp' and an optional signature set '*sigrdatasetp', with
+ * owner name '*namep', to section 'section', unless they are
+ * already there. Also add any pertinent additional data.
+ *
+ * If 'dbuf' is not NULL, then '*namep' is the name whose data is
+ * stored in 'dbuf'. In this case, query_addrrset() guarantees that
+ * when it returns the name will either have been kept or released.
+ */
+ result = dns_message_findname(client->message, section, name,
+ rdataset->type, rdataset->covers, &mname,
+ &mrdataset);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We've already got an RRset of the given name and type.
+ */
+ CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: dns_message_findname "
+ "succeeded: done");
+ if (dbuf != NULL) {
+ ns_client_releasename(client, namep);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_REQUIRED) != 0) {
+ mrdataset->attributes |= DNS_RDATASETATTR_REQUIRED;
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_STALE_ADDED) != 0)
+ {
+ mrdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED;
+ }
+ return;
+ } else if (result == DNS_R_NXDOMAIN) {
+ /*
+ * The name doesn't exist.
+ */
+ if (dbuf != NULL) {
+ ns_client_keepname(client, name, dbuf);
+ }
+ dns_message_addname(client->message, name, section);
+ *namep = NULL;
+ mname = name;
+ } else {
+ RUNTIME_CHECK(result == DNS_R_NXRRSET);
+ if (dbuf != NULL) {
+ ns_client_releasename(client, namep);
+ }
+ }
+
+ if (rdataset->trust != dns_trust_secure &&
+ (section == DNS_SECTION_ANSWER || section == DNS_SECTION_AUTHORITY))
+ {
+ client->query.attributes &= ~NS_QUERYATTR_SECURE;
+ }
+
+ /*
+ * Update message name, set rdataset order, and do additional
+ * section processing if needed.
+ */
+ query_addtoname(mname, rdataset);
+ query_setorder(qctx, mname, rdataset);
+ query_additional(qctx, mname, rdataset);
+
+ /*
+ * Note: we only add SIGs if we've added the type they cover, so
+ * we do not need to check if the SIG rdataset is already in the
+ * response.
+ */
+ *rdatasetp = NULL;
+ if (sigrdataset != NULL && dns_rdataset_isassociated(sigrdataset)) {
+ /*
+ * We have a signature. Add it to the response.
+ */
+ ISC_LIST_APPEND(mname->list, sigrdataset, link);
+ *sigrdatasetp = NULL;
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addrrset: done");
+}
+
+/*
+ * Mark the RRsets as secure. Update the cache (db) to reflect the
+ * change in trust level.
+ */
+static void
+mark_secure(ns_client_t *client, dns_db_t *db, dns_name_t *name,
+ dns_rdata_rrsig_t *rrsig, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ isc_stdtime_t now;
+
+ rdataset->trust = dns_trust_secure;
+ sigrdataset->trust = dns_trust_secure;
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Save the updated secure state. Ignore failures.
+ */
+ result = dns_db_findnodeext(db, name, true, &cm, &ci, &node);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ isc_stdtime_get(&now);
+ dns_rdataset_trimttl(rdataset, sigrdataset, rrsig, now,
+ client->view->acceptexpired);
+
+ (void)dns_db_addrdataset(db, node, NULL, client->now, rdataset, 0,
+ NULL);
+ (void)dns_db_addrdataset(db, node, NULL, client->now, sigrdataset, 0,
+ NULL);
+ dns_db_detachnode(db, &node);
+}
+
+/*
+ * Find the secure key that corresponds to rrsig.
+ * Note: 'keyrdataset' maintains state between successive calls,
+ * there may be multiple keys with the same keyid.
+ * Return false if we have exhausted all the possible keys.
+ */
+static bool
+get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig,
+ dns_rdataset_t *keyrdataset, dst_key_t **keyp) {
+ isc_result_t result;
+ dns_dbnode_t *node = NULL;
+ bool secure = false;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ if (!dns_rdataset_isassociated(keyrdataset)) {
+ result = dns_db_findnodeext(db, &rrsig->signer, false, &cm, &ci,
+ &node);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ result = dns_db_findrdataset(db, node, NULL,
+ dns_rdatatype_dnskey, 0,
+ client->now, keyrdataset, NULL);
+ dns_db_detachnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ if (keyrdataset->trust != dns_trust_secure) {
+ return (false);
+ }
+
+ result = dns_rdataset_first(keyrdataset);
+ } else {
+ result = dns_rdataset_next(keyrdataset);
+ }
+
+ for (; result == ISC_R_SUCCESS; result = dns_rdataset_next(keyrdataset))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t b;
+
+ dns_rdataset_current(keyrdataset, &rdata);
+ isc_buffer_init(&b, rdata.data, rdata.length);
+ isc_buffer_add(&b, rdata.length);
+ result = dst_key_fromdns(&rrsig->signer, rdata.rdclass, &b,
+ client->mctx, keyp);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ if (rrsig->algorithm == (dns_secalg_t)dst_key_alg(*keyp) &&
+ rrsig->keyid == (dns_keytag_t)dst_key_id(*keyp) &&
+ dst_key_iszonekey(*keyp))
+ {
+ secure = true;
+ break;
+ }
+ dst_key_free(keyp);
+ }
+ return (secure);
+}
+
+static bool
+verify(dst_key_t *key, dns_name_t *name, dns_rdataset_t *rdataset,
+ dns_rdata_t *rdata, ns_client_t *client) {
+ isc_result_t result;
+ dns_fixedname_t fixed;
+ bool ignore = false;
+
+ dns_fixedname_init(&fixed);
+
+again:
+ result = dns_dnssec_verify(name, rdataset, key, ignore,
+ client->view->maxbits, client->mctx, rdata,
+ NULL);
+ if (result == DNS_R_SIGEXPIRED && client->view->acceptexpired) {
+ ignore = true;
+ goto again;
+ }
+ if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) {
+ return (true);
+ }
+ return (false);
+}
+
+/*
+ * Validate the rdataset if possible with available records.
+ */
+static bool
+validate(ns_client_t *client, dns_db_t *db, dns_name_t *name,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_rrsig_t rrsig;
+ dst_key_t *key = NULL;
+ dns_rdataset_t keyrdataset;
+
+ if (sigrdataset == NULL || !dns_rdataset_isassociated(sigrdataset)) {
+ return (false);
+ }
+
+ for (result = dns_rdataset_first(sigrdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(sigrdataset))
+ {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (!dns_resolver_algorithm_supported(client->view->resolver,
+ name, rrsig.algorithm))
+ {
+ continue;
+ }
+ if (!dns_name_issubdomain(name, &rrsig.signer)) {
+ continue;
+ }
+ dns_rdataset_init(&keyrdataset);
+ do {
+ if (!get_key(client, db, &rrsig, &keyrdataset, &key)) {
+ break;
+ }
+ if (verify(key, name, rdataset, &rdata, client)) {
+ dst_key_free(&key);
+ dns_rdataset_disassociate(&keyrdataset);
+ mark_secure(client, db, name, &rrsig, rdataset,
+ sigrdataset);
+ return (true);
+ }
+ dst_key_free(&key);
+ } while (1);
+ if (dns_rdataset_isassociated(&keyrdataset)) {
+ dns_rdataset_disassociate(&keyrdataset);
+ }
+ }
+ return (false);
+}
+
+static void
+fixrdataset(ns_client_t *client, dns_rdataset_t **rdataset) {
+ if (*rdataset == NULL) {
+ *rdataset = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(*rdataset)) {
+ dns_rdataset_disassociate(*rdataset);
+ }
+}
+
+static void
+fixfname(ns_client_t *client, dns_name_t **fname, isc_buffer_t **dbuf,
+ isc_buffer_t *nbuf) {
+ if (*fname == NULL) {
+ *dbuf = ns_client_getnamebuf(client);
+ if (*dbuf == NULL) {
+ return;
+ }
+ *fname = ns_client_newname(client, *dbuf, nbuf);
+ }
+}
+
+static void
+free_devent(ns_client_t *client, isc_event_t **eventp,
+ dns_fetchevent_t **deventp) {
+ dns_fetchevent_t *devent = *deventp;
+
+ REQUIRE((void *)(*eventp) == (void *)(*deventp));
+
+ CTRACE(ISC_LOG_DEBUG(3), "free_devent");
+
+ if (devent->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);
+ }
+ if (devent->rdataset != NULL) {
+ ns_client_putrdataset(client, &devent->rdataset);
+ }
+ if (devent->sigrdataset != NULL) {
+ ns_client_putrdataset(client, &devent->sigrdataset);
+ }
+
+ /*
+ * If the two pointers are the same then leave the setting of
+ * (*deventp) to NULL to isc_event_free.
+ */
+ if ((void *)eventp != (void *)deventp) {
+ (*deventp) = NULL;
+ }
+ isc_event_free(eventp);
+}
+
+static void
+prefetch_done(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *devent = (dns_fetchevent_t *)event;
+ ns_client_t *client;
+
+ UNUSED(task);
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE);
+ client = devent->ev_arg;
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(task == client->task);
+
+ CTRACE(ISC_LOG_DEBUG(3), "prefetch_done");
+
+ LOCK(&client->query.fetchlock);
+ if (client->query.prefetch != NULL) {
+ INSIST(devent->fetch == client->query.prefetch);
+ client->query.prefetch = NULL;
+ }
+ UNLOCK(&client->query.fetchlock);
+
+ /*
+ * We're done prefetching, detach from quota.
+ */
+ if (client->recursionquota != NULL) {
+ isc_quota_detach(&client->recursionquota);
+ ns_stats_decrement(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ }
+
+ free_devent(client, &event, &devent);
+ isc_nmhandle_detach(&client->prefetchhandle);
+}
+
+static void
+query_prefetch(ns_client_t *client, dns_name_t *qname,
+ dns_rdataset_t *rdataset) {
+ isc_result_t result;
+ isc_sockaddr_t *peeraddr;
+ dns_rdataset_t *tmprdataset;
+ unsigned int options;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_prefetch");
+
+ if (client->query.prefetch != NULL ||
+ client->view->prefetch_trigger == 0U ||
+ rdataset->ttl > client->view->prefetch_trigger ||
+ (rdataset->attributes & DNS_RDATASETATTR_PREFETCH) == 0)
+ {
+ return;
+ }
+
+ if (client->recursionquota == NULL) {
+ result = isc_quota_attach(&client->sctx->recursionquota,
+ &client->recursionquota);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ break;
+ case ISC_R_SOFTQUOTA:
+ isc_quota_detach(&client->recursionquota);
+ FALLTHROUGH;
+ default:
+ return;
+ }
+ }
+
+ tmprdataset = ns_client_newrdataset(client);
+ if (tmprdataset == NULL) {
+ return;
+ }
+
+ if (!TCP(client)) {
+ peeraddr = &client->peeraddr;
+ } else {
+ peeraddr = NULL;
+ }
+
+ isc_nmhandle_attach(client->handle, &client->prefetchhandle);
+ options = client->query.fetchoptions | DNS_FETCHOPT_PREFETCH;
+ result = dns_resolver_createfetch(
+ client->view->resolver, qname, rdataset->type, NULL, NULL, NULL,
+ peeraddr, client->message->id, options, 0, NULL, client->task,
+ prefetch_done, client, tmprdataset, NULL,
+ &client->query.prefetch);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_putrdataset(client, &tmprdataset);
+ isc_nmhandle_detach(&client->prefetchhandle);
+ }
+
+ dns_rdataset_clearprefetch(rdataset);
+ ns_stats_increment(client->sctx->nsstats, ns_statscounter_prefetch);
+}
+
+static void
+rpz_clean(dns_zone_t **zonep, dns_db_t **dbp, dns_dbnode_t **nodep,
+ dns_rdataset_t **rdatasetp) {
+ if (nodep != NULL && *nodep != NULL) {
+ REQUIRE(dbp != NULL && *dbp != NULL);
+ dns_db_detachnode(*dbp, nodep);
+ }
+ if (dbp != NULL && *dbp != NULL) {
+ dns_db_detach(dbp);
+ }
+ if (zonep != NULL && *zonep != NULL) {
+ dns_zone_detach(zonep);
+ }
+ if (rdatasetp != NULL && *rdatasetp != NULL &&
+ dns_rdataset_isassociated(*rdatasetp))
+ {
+ dns_rdataset_disassociate(*rdatasetp);
+ }
+}
+
+static void
+rpz_match_clear(dns_rpz_st_t *st) {
+ rpz_clean(&st->m.zone, &st->m.db, &st->m.node, &st->m.rdataset);
+ st->m.version = NULL;
+}
+
+static isc_result_t
+rpz_ready(ns_client_t *client, dns_rdataset_t **rdatasetp) {
+ REQUIRE(rdatasetp != NULL);
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_ready");
+
+ if (*rdatasetp == NULL) {
+ *rdatasetp = ns_client_newrdataset(client);
+ if (*rdatasetp == NULL) {
+ CTRACE(ISC_LOG_ERROR, "rpz_ready: "
+ "ns_client_newrdataset failed");
+ return (DNS_R_SERVFAIL);
+ }
+ } else if (dns_rdataset_isassociated(*rdatasetp)) {
+ dns_rdataset_disassociate(*rdatasetp);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rpz_st_clear(ns_client_t *client) {
+ dns_rpz_st_t *st = client->query.rpz_st;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_st_clear");
+
+ if (st->m.rdataset != NULL) {
+ ns_client_putrdataset(client, &st->m.rdataset);
+ }
+ rpz_match_clear(st);
+
+ rpz_clean(NULL, &st->r.db, NULL, NULL);
+ if (st->r.ns_rdataset != NULL) {
+ ns_client_putrdataset(client, &st->r.ns_rdataset);
+ }
+ if (st->r.r_rdataset != NULL) {
+ ns_client_putrdataset(client, &st->r.r_rdataset);
+ }
+
+ rpz_clean(&st->q.zone, &st->q.db, &st->q.node, NULL);
+ if (st->q.rdataset != NULL) {
+ ns_client_putrdataset(client, &st->q.rdataset);
+ }
+ if (st->q.sigrdataset != NULL) {
+ ns_client_putrdataset(client, &st->q.sigrdataset);
+ }
+ st->state = 0;
+ st->m.type = DNS_RPZ_TYPE_BAD;
+ st->m.policy = DNS_RPZ_POLICY_MISS;
+ if (st->rpsdb != NULL) {
+ dns_db_detach(&st->rpsdb);
+ }
+}
+
+static dns_rpz_zbits_t
+rpz_get_zbits(ns_client_t *client, dns_rdatatype_t ip_type,
+ dns_rpz_type_t rpz_type) {
+ dns_rpz_st_t *st;
+ dns_rpz_zbits_t zbits = 0;
+
+ REQUIRE(client != NULL);
+ REQUIRE(client->query.rpz_st != NULL);
+
+ st = client->query.rpz_st;
+
+#ifdef USE_DNSRPS
+ if (st->popt.dnsrps_enabled) {
+ if (st->rpsdb == NULL ||
+ librpz->have_trig(dns_dnsrps_type2trig(rpz_type),
+ ip_type == dns_rdatatype_aaaa,
+ ((rpsdb_t *)st->rpsdb)->rsp))
+ {
+ return (DNS_RPZ_ALL_ZBITS);
+ }
+ return (0);
+ }
+#endif /* ifdef USE_DNSRPS */
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ zbits = st->have.client_ip;
+ break;
+ case DNS_RPZ_TYPE_QNAME:
+ zbits = st->have.qname;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ if (ip_type == dns_rdatatype_a) {
+ zbits = st->have.ipv4;
+ } else if (ip_type == dns_rdatatype_aaaa) {
+ zbits = st->have.ipv6;
+ } else {
+ zbits = st->have.ip;
+ }
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ zbits = st->have.nsdname;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ if (ip_type == dns_rdatatype_a) {
+ zbits = st->have.nsipv4;
+ } else if (ip_type == dns_rdatatype_aaaa) {
+ zbits = st->have.nsipv6;
+ } else {
+ zbits = st->have.nsip;
+ }
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * Choose
+ * the earliest configured policy zone (rpz->num)
+ * QNAME over IP over NSDNAME over NSIP (rpz_type)
+ * the smallest name,
+ * the longest IP address prefix,
+ * the lexically smallest address.
+ */
+ if (st->m.policy != DNS_RPZ_POLICY_MISS) {
+ if (st->m.type >= rpz_type) {
+ zbits &= DNS_RPZ_ZMASK(st->m.rpz->num);
+ } else {
+ zbits &= DNS_RPZ_ZMASK(st->m.rpz->num) >> 1;
+ }
+ }
+
+ /*
+ * If the client wants recursion, allow only compatible policies.
+ */
+ if (!RECURSIONOK(client)) {
+ zbits &= st->popt.no_rd_ok;
+ }
+
+ return (zbits);
+}
+
+static void
+query_rpzfetch(ns_client_t *client, dns_name_t *qname, dns_rdatatype_t type) {
+ isc_result_t result;
+ isc_sockaddr_t *peeraddr;
+ dns_rdataset_t *tmprdataset;
+ unsigned int options;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_rpzfetch");
+
+ if (client->query.prefetch != NULL) {
+ return;
+ }
+
+ if (client->recursionquota == NULL) {
+ result = isc_quota_attach(&client->sctx->recursionquota,
+ &client->recursionquota);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ break;
+ case ISC_R_SOFTQUOTA:
+ isc_quota_detach(&client->recursionquota);
+ FALLTHROUGH;
+ default:
+ return;
+ }
+ }
+
+ tmprdataset = ns_client_newrdataset(client);
+ if (tmprdataset == NULL) {
+ return;
+ }
+
+ if (!TCP(client)) {
+ peeraddr = &client->peeraddr;
+ } else {
+ peeraddr = NULL;
+ }
+
+ options = client->query.fetchoptions;
+ isc_nmhandle_attach(client->handle, &client->prefetchhandle);
+ result = dns_resolver_createfetch(
+ client->view->resolver, qname, type, NULL, NULL, NULL, peeraddr,
+ client->message->id, options, 0, NULL, client->task,
+ prefetch_done, client, tmprdataset, NULL,
+ &client->query.prefetch);
+ if (result != ISC_R_SUCCESS) {
+ ns_client_putrdataset(client, &tmprdataset);
+ isc_nmhandle_detach(&client->prefetchhandle);
+ }
+}
+
+/*
+ * Get an NS, A, or AAAA rrset related to the response for the client
+ * to check the contents of that rrset for hits by eligible policy zones.
+ */
+static isc_result_t
+rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
+ unsigned int options, dns_rpz_type_t rpz_type, dns_db_t **dbp,
+ dns_dbversion_t *version, dns_rdataset_t **rdatasetp,
+ bool resuming) {
+ dns_rpz_st_t *st;
+ bool is_zone;
+ dns_dbnode_t *node;
+ dns_fixedname_t fixed;
+ dns_name_t *found;
+ isc_result_t result;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rrset_find");
+
+ st = client->query.rpz_st;
+ if ((st->state & DNS_RPZ_RECURSING) != 0) {
+ INSIST(st->r.r_type == type);
+ INSIST(dns_name_equal(name, st->r_name));
+ INSIST(*rdatasetp == NULL ||
+ !dns_rdataset_isassociated(*rdatasetp));
+ st->state &= ~DNS_RPZ_RECURSING;
+ RESTORE(*dbp, st->r.db);
+ if (*rdatasetp != NULL) {
+ ns_client_putrdataset(client, rdatasetp);
+ }
+ RESTORE(*rdatasetp, st->r.r_rdataset);
+ result = st->r.r_result;
+ if (result == DNS_R_DELEGATION) {
+ CTRACE(ISC_LOG_ERROR, "RPZ recursing");
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+ rpz_type, "rpz_rrset_find(1)", result);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ result = DNS_R_SERVFAIL;
+ }
+ return (result);
+ }
+
+ result = rpz_ready(client, rdatasetp);
+ if (result != ISC_R_SUCCESS) {
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (result);
+ }
+ if (*dbp != NULL) {
+ is_zone = false;
+ } else {
+ dns_zone_t *zone;
+
+ version = NULL;
+ zone = NULL;
+ result = query_getdb(client, name, type, 0, &zone, dbp,
+ &version, &is_zone);
+ if (result != ISC_R_SUCCESS) {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+ rpz_type, "rpz_rrset_find(2)", result);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ return (result);
+ }
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ }
+
+ node = NULL;
+ found = dns_fixedname_initname(&fixed);
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+ result = dns_db_findext(*dbp, name, version, type, options, client->now,
+ &node, found, &cm, &ci, *rdatasetp, NULL);
+ if (result == DNS_R_DELEGATION && is_zone && USECACHE(client)) {
+ /*
+ * Try the cache if we're authoritative for an
+ * ancestor but not the domain itself.
+ */
+ rpz_clean(NULL, dbp, &node, rdatasetp);
+ version = NULL;
+ dns_db_attach(client->view->cachedb, dbp);
+ result = dns_db_findext(*dbp, name, version, type, 0,
+ client->now, &node, found, &cm, &ci,
+ *rdatasetp, NULL);
+ }
+ rpz_clean(NULL, dbp, &node, NULL);
+ if (result == DNS_R_DELEGATION) {
+ rpz_clean(NULL, NULL, NULL, rdatasetp);
+ /*
+ * Recurse for NS rrset or A or AAAA rrset for an NS.
+ * Do not recurse for addresses for the query name.
+ */
+ if (rpz_type == DNS_RPZ_TYPE_IP) {
+ result = DNS_R_NXRRSET;
+ } else if (!client->view->rpzs->p.nsip_wait_recurse ||
+ (!client->view->rpzs->p.nsdname_wait_recurse &&
+ rpz_type == DNS_RPZ_TYPE_NSDNAME))
+ {
+ query_rpzfetch(client, name, type);
+ result = DNS_R_NXRRSET;
+ } else {
+ dns_name_copy(name, st->r_name);
+ result = ns_query_recurse(client, type, st->r_name,
+ NULL, NULL, resuming);
+ if (result == ISC_R_SUCCESS) {
+ st->state |= DNS_RPZ_RECURSING;
+ result = DNS_R_DELEGATION;
+ }
+ }
+ }
+ return (result);
+}
+
+/*
+ * Compute a policy owner name, p_name, in a policy zone given the needed
+ * policy type and the trigger name.
+ */
+static isc_result_t
+rpz_get_p_name(ns_client_t *client, dns_name_t *p_name, dns_rpz_zone_t *rpz,
+ dns_rpz_type_t rpz_type, dns_name_t *trig_name) {
+ dns_offsets_t prefix_offsets;
+ dns_name_t prefix, *suffix;
+ unsigned int first, labels;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_get_p_name");
+
+ /*
+ * The policy owner name consists of a suffix depending on the type
+ * and policy zone and a prefix that is the longest possible string
+ * from the trigger name that keesp the resulting policy owner name
+ * from being too long.
+ */
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ suffix = &rpz->client_ip;
+ break;
+ case DNS_RPZ_TYPE_QNAME:
+ suffix = &rpz->origin;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ suffix = &rpz->ip;
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ suffix = &rpz->nsdname;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ suffix = &rpz->nsip;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * Start with relative version of the full trigger name,
+ * and trim enough allow the addition of the suffix.
+ */
+ dns_name_init(&prefix, prefix_offsets);
+ labels = dns_name_countlabels(trig_name);
+ first = 0;
+ for (;;) {
+ dns_name_getlabelsequence(trig_name, first, labels - first - 1,
+ &prefix);
+ result = dns_name_concatenate(&prefix, suffix, p_name, NULL);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ INSIST(result == DNS_R_NAMETOOLONG);
+ /*
+ * Trim the trigger name until the combination is not too long.
+ */
+ if (labels - first < 2) {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, suffix,
+ rpz_type, "concatenate()", result);
+ return (ISC_R_FAILURE);
+ }
+ /*
+ * Complain once about trimming the trigger name.
+ */
+ if (first == 0) {
+ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, suffix,
+ rpz_type, "concatenate()", result);
+ }
+ ++first;
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Look in policy zone rpz for a policy of rpz_type by p_name.
+ * The self-name (usually the client qname or an NS name) is compared with
+ * the target of a CNAME policy for the old style passthru encoding.
+ * If found, the policy is recorded in *zonep, *dbp, *versionp, *nodep,
+ * *rdatasetp, and *policyp.
+ * The target DNS type, qtype, chooses the best rdataset for *rdatasetp.
+ * The caller must decide if the found policy is most suitable, including
+ * better than a previously found policy.
+ * If it is best, the caller records it in client->query.rpz_st->m.
+ */
+static isc_result_t
+rpz_find_p(ns_client_t *client, dns_name_t *self_name, dns_rdatatype_t qtype,
+ dns_name_t *p_name, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ dns_zone_t **zonep, dns_db_t **dbp, dns_dbversion_t **versionp,
+ dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp,
+ dns_rpz_policy_t *policyp) {
+ dns_fixedname_t foundf;
+ dns_name_t *found;
+ isc_result_t result;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ bool found_a = false;
+
+ REQUIRE(nodep != NULL);
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_find_p");
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Try to find either a CNAME or the type of record demanded by the
+ * request from the policy zone.
+ */
+ rpz_clean(zonep, dbp, nodep, rdatasetp);
+ result = rpz_ready(client, rdatasetp);
+ if (result != ISC_R_SUCCESS) {
+ CTRACE(ISC_LOG_ERROR, "rpz_ready() failed");
+ return (DNS_R_SERVFAIL);
+ }
+ *versionp = NULL;
+ result = rpz_getdb(client, p_name, rpz_type, zonep, dbp, versionp);
+ if (result != ISC_R_SUCCESS) {
+ return (DNS_R_NXDOMAIN);
+ }
+ found = dns_fixedname_initname(&foundf);
+
+ result = dns_db_findext(*dbp, p_name, *versionp, dns_rdatatype_any, 0,
+ client->now, nodep, found, &cm, &ci, *rdatasetp,
+ NULL);
+ /*
+ * Choose the best rdataset if we found something.
+ */
+ if (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_t *rdsiter;
+
+ rdsiter = NULL;
+ result = dns_db_allrdatasets(*dbp, *nodep, *versionp, 0, 0,
+ &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name,
+ rpz_type, "allrdatasets()", result);
+ CTRACE(ISC_LOG_ERROR,
+ "rpz_find_p: allrdatasets failed");
+ return (DNS_R_SERVFAIL);
+ }
+ if (qtype == dns_rdatatype_aaaa &&
+ !ISC_LIST_EMPTY(client->view->dns64))
+ {
+ for (result = dns_rdatasetiter_first(rdsiter);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, *rdatasetp);
+ if ((*rdatasetp)->type == dns_rdatatype_a) {
+ found_a = true;
+ }
+ dns_rdataset_disassociate(*rdatasetp);
+ }
+ }
+ for (result = dns_rdatasetiter_first(rdsiter);
+ result == ISC_R_SUCCESS;
+ result = dns_rdatasetiter_next(rdsiter))
+ {
+ dns_rdatasetiter_current(rdsiter, *rdatasetp);
+ if ((*rdatasetp)->type == dns_rdatatype_cname ||
+ (*rdatasetp)->type == qtype)
+ {
+ break;
+ }
+ dns_rdataset_disassociate(*rdatasetp);
+ }
+ dns_rdatasetiter_destroy(&rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ if (result != ISC_R_NOMORE) {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL,
+ p_name, rpz_type, "rdatasetiter",
+ result);
+ CTRACE(ISC_LOG_ERROR, "rpz_find_p: "
+ "rdatasetiter failed");
+ return (DNS_R_SERVFAIL);
+ }
+ /*
+ * Ask again to get the right DNS_R_DNAME/NXRRSET/...
+ * result if there is neither a CNAME nor target type.
+ */
+ if (dns_rdataset_isassociated(*rdatasetp)) {
+ dns_rdataset_disassociate(*rdatasetp);
+ }
+ dns_db_detachnode(*dbp, nodep);
+
+ if (qtype == dns_rdatatype_rrsig ||
+ qtype == dns_rdatatype_sig)
+ {
+ result = DNS_R_NXRRSET;
+ } else {
+ result = dns_db_findext(*dbp, p_name, *versionp,
+ qtype, 0, client->now,
+ nodep, found, &cm, &ci,
+ *rdatasetp, NULL);
+ }
+ }
+ }
+ switch (result) {
+ case ISC_R_SUCCESS:
+ if ((*rdatasetp)->type != dns_rdatatype_cname) {
+ *policyp = DNS_RPZ_POLICY_RECORD;
+ } else {
+ *policyp = dns_rpz_decode_cname(rpz, *rdatasetp,
+ self_name);
+ if ((*policyp == DNS_RPZ_POLICY_RECORD ||
+ *policyp == DNS_RPZ_POLICY_WILDCNAME) &&
+ qtype != dns_rdatatype_cname &&
+ qtype != dns_rdatatype_any)
+ {
+ return (DNS_R_CNAME);
+ }
+ }
+ return (ISC_R_SUCCESS);
+ case DNS_R_NXRRSET:
+ if (found_a) {
+ *policyp = DNS_RPZ_POLICY_DNS64;
+ } else {
+ *policyp = DNS_RPZ_POLICY_NODATA;
+ }
+ return (result);
+ case DNS_R_DNAME:
+ /*
+ * DNAME policy RRs have very few if any uses that are not
+ * better served with simple wildcards. Making them work would
+ * require complications to get the number of labels matched
+ * in the name or the found name to the main DNS_R_DNAME case
+ * in query_dname(). The domain also does not appear in the
+ * summary database at the right level, so this happens only
+ * with a single policy zone when we have no summary database.
+ * Treat it as a miss.
+ */
+ case DNS_R_NXDOMAIN:
+ case DNS_R_EMPTYNAME:
+ return (DNS_R_NXDOMAIN);
+ default:
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, p_name, rpz_type, "",
+ result);
+ CTRACE(ISC_LOG_ERROR, "rpz_find_p: unexpected result");
+ return (DNS_R_SERVFAIL);
+ }
+}
+
+static void
+rpz_save_p(dns_rpz_st_t *st, dns_rpz_zone_t *rpz, dns_rpz_type_t rpz_type,
+ dns_rpz_policy_t policy, dns_name_t *p_name, dns_rpz_prefix_t prefix,
+ isc_result_t result, dns_zone_t **zonep, dns_db_t **dbp,
+ dns_dbnode_t **nodep, dns_rdataset_t **rdatasetp,
+ dns_dbversion_t *version) {
+ dns_rdataset_t *trdataset = NULL;
+
+ rpz_match_clear(st);
+ st->m.rpz = rpz;
+ st->m.type = rpz_type;
+ st->m.policy = policy;
+ dns_name_copy(p_name, st->p_name);
+ st->m.prefix = prefix;
+ st->m.result = result;
+ SAVE(st->m.zone, *zonep);
+ SAVE(st->m.db, *dbp);
+ SAVE(st->m.node, *nodep);
+ if (*rdatasetp != NULL && dns_rdataset_isassociated(*rdatasetp)) {
+ /*
+ * Save the replacement rdataset from the policy
+ * and make the previous replacement rdataset scratch.
+ */
+ SAVE(trdataset, st->m.rdataset);
+ SAVE(st->m.rdataset, *rdatasetp);
+ SAVE(*rdatasetp, trdataset);
+ st->m.ttl = ISC_MIN(st->m.rdataset->ttl, rpz->max_policy_ttl);
+ } else {
+ st->m.ttl = ISC_MIN(DNS_RPZ_TTL_DEFAULT, rpz->max_policy_ttl);
+ }
+ SAVE(st->m.version, version);
+}
+
+#ifdef USE_DNSRPS
+/*
+ * Check the results of a RPZ service interface lookup.
+ * Stop after an error (<0) or not a hit on a disabled zone (0).
+ * Continue after a hit on a disabled zone (>0).
+ */
+static int
+dnsrps_ck(librpz_emsg_t *emsg, ns_client_t *client, rpsdb_t *rpsdb,
+ bool recursed) {
+ isc_region_t region;
+ librpz_domain_buf_t pname_buf;
+
+ if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) {
+ return (-1);
+ }
+
+ /*
+ * Forget the state from before the IP address or domain check
+ * if the lookup hit nothing.
+ */
+ if (rpsdb->result.policy == LIBRPZ_POLICY_UNDEFINED ||
+ rpsdb->result.hit_id != rpsdb->hit_id ||
+ rpsdb->result.policy != LIBRPZ_POLICY_DISABLED)
+ {
+ if (!librpz->rsp_pop_discard(emsg, rpsdb->rsp)) {
+ return (-1);
+ }
+ return (0);
+ }
+
+ /*
+ * Log a hit on a disabled zone.
+ * Forget the zone to not try it again, and restore the pre-hit state.
+ */
+ if (!librpz->rsp_domain(emsg, &pname_buf, rpsdb->rsp)) {
+ return (-1);
+ }
+ region.base = pname_buf.d;
+ region.length = pname_buf.size;
+ dns_name_fromregion(client->query.rpz_st->p_name, &region);
+ rpz_log_rewrite(client, true, dns_dnsrps_2policy(rpsdb->result.zpolicy),
+ dns_dnsrps_trig2type(rpsdb->result.trig), NULL,
+ client->query.rpz_st->p_name, NULL,
+ rpsdb->result.cznum);
+
+ if (!librpz->rsp_forget_zone(emsg, rpsdb->result.cznum, rpsdb->rsp) ||
+ !librpz->rsp_pop(emsg, &rpsdb->result, rpsdb->rsp))
+ {
+ return (-1);
+ }
+ return (1);
+}
+
+/*
+ * Ready the shim database and rdataset for a DNSRPS hit.
+ */
+static bool
+dnsrps_set_p(librpz_emsg_t *emsg, ns_client_t *client, dns_rpz_st_t *st,
+ dns_rdatatype_t qtype, dns_rdataset_t **p_rdatasetp,
+ bool recursed) {
+ rpsdb_t *rpsdb;
+ librpz_domain_buf_t pname_buf;
+ isc_region_t region;
+ dns_zone_t *p_zone;
+ dns_db_t *p_db;
+ dns_dbnode_t *p_node;
+ dns_rpz_policy_t policy;
+ dns_fixedname_t foundf;
+ dns_name_t *found;
+ dns_rdatatype_t foundtype, searchtype;
+ isc_result_t result;
+
+ rpsdb = (rpsdb_t *)st->rpsdb;
+
+ if (!librpz->rsp_result(emsg, &rpsdb->result, recursed, rpsdb->rsp)) {
+ return (false);
+ }
+
+ if (rpsdb->result.policy == LIBRPZ_POLICY_UNDEFINED) {
+ return (true);
+ }
+
+ /*
+ * Give the fake or shim DNSRPS database its new origin.
+ */
+ if (!librpz->rsp_soa(emsg, NULL, NULL, &rpsdb->origin_buf,
+ &rpsdb->result, rpsdb->rsp))
+ {
+ return (false);
+ }
+ region.base = rpsdb->origin_buf.d;
+ region.length = rpsdb->origin_buf.size;
+ dns_name_fromregion(&rpsdb->common.origin, &region);
+
+ if (!librpz->rsp_domain(emsg, &pname_buf, rpsdb->rsp)) {
+ return (false);
+ }
+ region.base = pname_buf.d;
+ region.length = pname_buf.size;
+ dns_name_fromregion(st->p_name, &region);
+
+ p_zone = NULL;
+ p_db = NULL;
+ p_node = NULL;
+ rpz_ready(client, p_rdatasetp);
+ dns_db_attach(st->rpsdb, &p_db);
+ policy = dns_dnsrps_2policy(rpsdb->result.policy);
+ if (policy != DNS_RPZ_POLICY_RECORD) {
+ result = ISC_R_SUCCESS;
+ } else if (qtype == dns_rdatatype_rrsig) {
+ /*
+ * dns_find_db() refuses to look for and fail to
+ * find dns_rdatatype_rrsig.
+ */
+ result = DNS_R_NXRRSET;
+ policy = DNS_RPZ_POLICY_NODATA;
+ } else {
+ /*
+ * Get the next (and so first) RR from the policy node.
+ * If it is a CNAME, then look for it regardless of the
+ * query type.
+ */
+ if (!librpz->rsp_rr(emsg, &foundtype, NULL, NULL, NULL,
+ &rpsdb->result, rpsdb->qname->ndata,
+ rpsdb->qname->length, rpsdb->rsp))
+ {
+ return (false);
+ }
+ if (foundtype == dns_rdatatype_cname) {
+ searchtype = dns_rdatatype_cname;
+ } else {
+ searchtype = qtype;
+ }
+ /*
+ * Get the DNSPRS imitation rdataset.
+ */
+ found = dns_fixedname_initname(&foundf);
+ result = dns_db_find(p_db, st->p_name, NULL, searchtype, 0, 0,
+ &p_node, found, *p_rdatasetp, NULL);
+
+ if (result == ISC_R_SUCCESS) {
+ if (searchtype == dns_rdatatype_cname &&
+ qtype != dns_rdatatype_cname)
+ {
+ result = DNS_R_CNAME;
+ }
+ } else if (result == DNS_R_NXRRSET) {
+ policy = DNS_RPZ_POLICY_NODATA;
+ } else {
+ snprintf(emsg->c, sizeof(emsg->c), "dns_db_find(): %s",
+ isc_result_totext(result));
+ return (false);
+ }
+ }
+
+ rpz_save_p(st, client->view->rpzs->zones[rpsdb->result.cznum],
+ dns_dnsrps_trig2type(rpsdb->result.trig), policy, st->p_name,
+ 0, result, &p_zone, &p_db, &p_node, p_rdatasetp, NULL);
+
+ rpz_clean(NULL, NULL, NULL, p_rdatasetp);
+
+ return (true);
+}
+
+static isc_result_t
+dnsrps_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr,
+ dns_rpz_type_t rpz_type, dns_rdataset_t **p_rdatasetp) {
+ dns_rpz_st_t *st;
+ rpsdb_t *rpsdb;
+ librpz_trig_t trig = LIBRPZ_TRIG_CLIENT_IP;
+ bool recursed = false;
+ int res;
+ librpz_emsg_t emsg;
+ isc_result_t result;
+
+ st = client->query.rpz_st;
+ rpsdb = (rpsdb_t *)st->rpsdb;
+
+ result = rpz_ready(client, p_rdatasetp);
+ if (result != ISC_R_SUCCESS) {
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (result);
+ }
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_CLIENT_IP:
+ trig = LIBRPZ_TRIG_CLIENT_IP;
+ recursed = false;
+ break;
+ case DNS_RPZ_TYPE_IP:
+ trig = LIBRPZ_TRIG_IP;
+ recursed = true;
+ break;
+ case DNS_RPZ_TYPE_NSIP:
+ trig = LIBRPZ_TRIG_NSIP;
+ recursed = true;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ do {
+ if (!librpz->rsp_push(&emsg, rpsdb->rsp) ||
+ !librpz->ck_ip(&emsg,
+ netaddr->family == AF_INET
+ ? (const void *)&netaddr->type.in
+ : (const void *)&netaddr->type.in6,
+ netaddr->family, trig, ++rpsdb->hit_id,
+ recursed, rpsdb->rsp) ||
+ (res = dnsrps_ck(&emsg, client, rpsdb, recursed)) < 0)
+ {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL,
+ rpz_type, emsg.c, DNS_R_SERVFAIL);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (DNS_R_SERVFAIL);
+ }
+ } while (res != 0);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+dnsrps_rewrite_name(ns_client_t *client, dns_name_t *trig_name, bool recursed,
+ dns_rpz_type_t rpz_type, dns_rdataset_t **p_rdatasetp) {
+ dns_rpz_st_t *st;
+ rpsdb_t *rpsdb;
+ librpz_trig_t trig = LIBRPZ_TRIG_CLIENT_IP;
+ isc_region_t r;
+ int res;
+ librpz_emsg_t emsg;
+ isc_result_t result;
+
+ st = client->query.rpz_st;
+ rpsdb = (rpsdb_t *)st->rpsdb;
+
+ result = rpz_ready(client, p_rdatasetp);
+ if (result != ISC_R_SUCCESS) {
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (result);
+ }
+
+ switch (rpz_type) {
+ case DNS_RPZ_TYPE_QNAME:
+ trig = LIBRPZ_TRIG_QNAME;
+ break;
+ case DNS_RPZ_TYPE_NSDNAME:
+ trig = LIBRPZ_TRIG_NSDNAME;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ dns_name_toregion(trig_name, &r);
+ do {
+ if (!librpz->rsp_push(&emsg, rpsdb->rsp) ||
+ !librpz->ck_domain(&emsg, r.base, r.length, trig,
+ ++rpsdb->hit_id, recursed, rpsdb->rsp) ||
+ (res = dnsrps_ck(&emsg, client, rpsdb, recursed)) < 0)
+ {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL,
+ rpz_type, emsg.c, DNS_R_SERVFAIL);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (DNS_R_SERVFAIL);
+ }
+ } while (res != 0);
+ return (ISC_R_SUCCESS);
+}
+#endif /* USE_DNSRPS */
+
+/*
+ * Check this address in every eligible policy zone.
+ */
+static isc_result_t
+rpz_rewrite_ip(ns_client_t *client, const isc_netaddr_t *netaddr,
+ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t zbits, dns_rdataset_t **p_rdatasetp) {
+ dns_rpz_zones_t *rpzs;
+ dns_rpz_st_t *st;
+ dns_rpz_zone_t *rpz;
+ dns_rpz_prefix_t prefix;
+ dns_rpz_num_t rpz_num;
+ dns_fixedname_t ip_namef, p_namef;
+ dns_name_t *ip_name, *p_name;
+ dns_zone_t *p_zone;
+ dns_db_t *p_db;
+ dns_dbversion_t *p_version;
+ dns_dbnode_t *p_node;
+ dns_rpz_policy_t policy;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip");
+
+ rpzs = client->view->rpzs;
+ st = client->query.rpz_st;
+#ifdef USE_DNSRPS
+ if (st->popt.dnsrps_enabled) {
+ return (dnsrps_rewrite_ip(client, netaddr, rpz_type,
+ p_rdatasetp));
+ }
+#endif /* ifdef USE_DNSRPS */
+
+ ip_name = dns_fixedname_initname(&ip_namef);
+
+ p_zone = NULL;
+ p_db = NULL;
+ p_node = NULL;
+
+ while (zbits != 0) {
+ rpz_num = dns_rpz_find_ip(rpzs, rpz_type, zbits, netaddr,
+ ip_name, &prefix);
+ if (rpz_num == DNS_RPZ_INVALID_NUM) {
+ break;
+ }
+ zbits &= (DNS_RPZ_ZMASK(rpz_num) >> 1);
+
+ /*
+ * Do not try applying policy zones that cannot replace a
+ * previously found policy zone.
+ * Stop looking if the next best choice cannot
+ * replace what we already have.
+ */
+ rpz = rpzs->zones[rpz_num];
+ if (st->m.policy != DNS_RPZ_POLICY_MISS) {
+ if (st->m.rpz->num < rpz->num) {
+ break;
+ }
+ if (st->m.rpz->num == rpz->num &&
+ (st->m.type < rpz_type || st->m.prefix > prefix))
+ {
+ break;
+ }
+ }
+
+ /*
+ * Get the policy for a prefix at least as long
+ * as the prefix of the entry we had before.
+ */
+ p_name = dns_fixedname_initname(&p_namef);
+ result = rpz_get_p_name(client, p_name, rpz, rpz_type, ip_name);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ result = rpz_find_p(client, ip_name, qtype, p_name, rpz,
+ rpz_type, &p_zone, &p_db, &p_version,
+ &p_node, p_rdatasetp, &policy);
+ switch (result) {
+ case DNS_R_NXDOMAIN:
+ /*
+ * Continue after a policy record that is missing
+ * contrary to the summary data. The summary
+ * data can out of date during races with and among
+ * policy zone updates.
+ */
+ CTRACE(ISC_LOG_ERROR, "rpz_rewrite_ip: mismatched "
+ "summary data; "
+ "continuing");
+ continue;
+ case DNS_R_SERVFAIL:
+ rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (DNS_R_SERVFAIL);
+ default:
+ /*
+ * Forget this policy if it is not preferable
+ * to the previously found policy.
+ * If this policy is not good, then stop looking
+ * because none of the later policy zones would work.
+ *
+ * With more than one applicable policy, prefer
+ * the earliest configured policy,
+ * client-IP over QNAME over IP over NSDNAME over NSIP,
+ * the longest prefix
+ * the lexically smallest address.
+ * dns_rpz_find_ip() ensures st->m.rpz->num >= rpz->num.
+ * We can compare new and current p_name because
+ * both are of the same type and in the same zone.
+ * The tests above eliminate other reasons to
+ * reject this policy. If this policy can't work,
+ * then neither can later zones.
+ */
+ if (st->m.policy != DNS_RPZ_POLICY_MISS &&
+ rpz->num == st->m.rpz->num &&
+ (st->m.type == rpz_type && st->m.prefix == prefix &&
+ 0 > dns_name_rdatacompare(st->p_name, p_name)))
+ {
+ break;
+ }
+
+ /*
+ * Stop checking after saving an enabled hit in this
+ * policy zone. The radix tree in the policy zone
+ * ensures that we found the longest match.
+ */
+ if (rpz->policy != DNS_RPZ_POLICY_DISABLED) {
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip: "
+ "rpz_save_p");
+ rpz_save_p(st, rpz, rpz_type, policy, p_name,
+ prefix, result, &p_zone, &p_db,
+ &p_node, p_rdatasetp, p_version);
+ break;
+ }
+
+ /*
+ * Log DNS_RPZ_POLICY_DISABLED zones
+ * and try the next eligible policy zone.
+ */
+ rpz_log_rewrite(client, true, policy, rpz_type, p_zone,
+ p_name, NULL, rpz_num);
+ }
+ }
+
+ rpz_clean(&p_zone, &p_db, &p_node, p_rdatasetp);
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Check the IP addresses in the A or AAAA rrsets for name against
+ * all eligible rpz_type (IP or NSIP) response policy rewrite rules.
+ */
+static isc_result_t
+rpz_rewrite_ip_rrset(ns_client_t *client, dns_name_t *name,
+ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+ dns_rdatatype_t ip_type, dns_db_t **ip_dbp,
+ dns_dbversion_t *ip_version, dns_rdataset_t **ip_rdatasetp,
+ dns_rdataset_t **p_rdatasetp, bool resuming) {
+ dns_rpz_zbits_t zbits;
+ isc_netaddr_t netaddr;
+ struct in_addr ina;
+ struct in6_addr in6a;
+ isc_result_t result;
+ unsigned int options = client->query.dboptions | DNS_DBFIND_GLUEOK;
+ bool done = false;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrset");
+
+ do {
+ zbits = rpz_get_zbits(client, ip_type, rpz_type);
+ if (zbits == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Get the A or AAAA rdataset.
+ */
+ result = rpz_rrset_find(client, name, ip_type, options,
+ rpz_type, ip_dbp, ip_version,
+ ip_rdatasetp, resuming);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_GLUE:
+ case DNS_R_ZONECUT:
+ break;
+ case DNS_R_EMPTYNAME:
+ case DNS_R_EMPTYWILD:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NXRRSET:
+ case DNS_R_NCACHENXRRSET:
+ case ISC_R_NOTFOUND:
+ return (ISC_R_SUCCESS);
+ case DNS_R_DELEGATION:
+ case DNS_R_DUPLICATE:
+ case DNS_R_DROP:
+ return (result);
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, name,
+ rpz_type, "NS address rewrite rrset",
+ result);
+ return (ISC_R_SUCCESS);
+ default:
+ if (client->query.rpz_st->m.policy !=
+ DNS_RPZ_POLICY_ERROR)
+ {
+ client->query.rpz_st->m.policy =
+ DNS_RPZ_POLICY_ERROR;
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, name,
+ rpz_type,
+ "NS address rewrite rrset",
+ result);
+ }
+ CTRACE(ISC_LOG_ERROR,
+ "rpz_rewrite_ip_rrset: unexpected "
+ "result");
+ return (DNS_R_SERVFAIL);
+ }
+
+ /*
+ * If we are processing glue setup for the next loop
+ * otherwise we are done.
+ */
+ if (result == DNS_R_GLUE) {
+ options = client->query.dboptions;
+ } else {
+ options = client->query.dboptions | DNS_DBFIND_GLUEOK;
+ done = true;
+ }
+
+ /*
+ * Check all of the IP addresses in the rdataset.
+ */
+ for (result = dns_rdataset_first(*ip_rdatasetp);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(*ip_rdatasetp))
+ {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(*ip_rdatasetp, &rdata);
+ switch (rdata.type) {
+ case dns_rdatatype_a:
+ INSIST(rdata.length == 4);
+ memmove(&ina.s_addr, rdata.data, 4);
+ isc_netaddr_fromin(&netaddr, &ina);
+ break;
+ case dns_rdatatype_aaaa:
+ INSIST(rdata.length == 16);
+ memmove(in6a.s6_addr, rdata.data, 16);
+ isc_netaddr_fromin6(&netaddr, &in6a);
+ break;
+ default:
+ continue;
+ }
+
+ result = rpz_rewrite_ip(client, &netaddr, qtype,
+ rpz_type, zbits, p_rdatasetp);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ }
+ } while (!done &&
+ client->query.rpz_st->m.policy == DNS_RPZ_POLICY_MISS);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*
+ * Look for IP addresses in A and AAAA rdatasets
+ * that trigger all eligible IP or NSIP policy rules.
+ */
+static isc_result_t
+rpz_rewrite_ip_rrsets(ns_client_t *client, dns_name_t *name,
+ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+ dns_rdataset_t **ip_rdatasetp, bool resuming) {
+ dns_rpz_st_t *st;
+ dns_dbversion_t *ip_version;
+ dns_db_t *ip_db;
+ dns_rdataset_t *p_rdataset;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ip_rrsets");
+
+ st = client->query.rpz_st;
+ ip_version = NULL;
+ ip_db = NULL;
+ p_rdataset = NULL;
+ if ((st->state & DNS_RPZ_DONE_IPv4) == 0 &&
+ (qtype == dns_rdatatype_a || qtype == dns_rdatatype_any ||
+ rpz_type == DNS_RPZ_TYPE_NSIP))
+ {
+ /*
+ * Rewrite based on an IPv4 address that will appear
+ * in the ANSWER section or if we are checking IP addresses.
+ */
+ result = rpz_rewrite_ip_rrset(
+ client, name, qtype, rpz_type, dns_rdatatype_a, &ip_db,
+ ip_version, ip_rdatasetp, &p_rdataset, resuming);
+ if (result == ISC_R_SUCCESS) {
+ st->state |= DNS_RPZ_DONE_IPv4;
+ }
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+ if (result == ISC_R_SUCCESS &&
+ (qtype == dns_rdatatype_aaaa || qtype == dns_rdatatype_any ||
+ rpz_type == DNS_RPZ_TYPE_NSIP))
+ {
+ /*
+ * Rewrite based on IPv6 addresses that will appear
+ * in the ANSWER section or if we are checking IP addresses.
+ */
+ result = rpz_rewrite_ip_rrset(client, name, qtype, rpz_type,
+ dns_rdatatype_aaaa, &ip_db,
+ ip_version, ip_rdatasetp,
+ &p_rdataset, resuming);
+ }
+ if (ip_db != NULL) {
+ dns_db_detach(&ip_db);
+ }
+ ns_client_putrdataset(client, &p_rdataset);
+ return (result);
+}
+
+/*
+ * Try to rewrite a request for a qtype rdataset based on the trigger name
+ * trig_name and rpz_type (DNS_RPZ_TYPE_QNAME or DNS_RPZ_TYPE_NSDNAME).
+ * Record the results including the replacement rdataset if any
+ * in client->query.rpz_st.
+ * *rdatasetp is a scratch rdataset.
+ */
+static isc_result_t
+rpz_rewrite_name(ns_client_t *client, dns_name_t *trig_name,
+ dns_rdatatype_t qtype, dns_rpz_type_t rpz_type,
+ dns_rpz_zbits_t allowed_zbits, bool recursed,
+ dns_rdataset_t **rdatasetp) {
+ dns_rpz_zones_t *rpzs;
+ dns_rpz_zone_t *rpz;
+ dns_rpz_st_t *st;
+ dns_fixedname_t p_namef;
+ dns_name_t *p_name;
+ dns_rpz_zbits_t zbits;
+ dns_rpz_num_t rpz_num;
+ dns_zone_t *p_zone;
+ dns_db_t *p_db;
+ dns_dbversion_t *p_version;
+ dns_dbnode_t *p_node;
+ dns_rpz_policy_t policy;
+ isc_result_t result;
+
+#ifndef USE_DNSRPS
+ UNUSED(recursed);
+#endif /* ifndef USE_DNSRPS */
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name");
+
+ rpzs = client->view->rpzs;
+ st = client->query.rpz_st;
+
+#ifdef USE_DNSRPS
+ if (st->popt.dnsrps_enabled) {
+ return (dnsrps_rewrite_name(client, trig_name, recursed,
+ rpz_type, rdatasetp));
+ }
+#endif /* ifdef USE_DNSRPS */
+
+ zbits = rpz_get_zbits(client, qtype, rpz_type);
+ zbits &= allowed_zbits;
+ if (zbits == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Use the summary database to find the bit mask of policy zones
+ * with policies for this trigger name. We do this even if there
+ * is only one eligible policy zone so that wildcard triggers
+ * are matched correctly, and not into their parent.
+ */
+ zbits = dns_rpz_find_name(rpzs, rpz_type, zbits, trig_name);
+ if (zbits == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ p_name = dns_fixedname_initname(&p_namef);
+
+ p_zone = NULL;
+ p_db = NULL;
+ p_node = NULL;
+
+ /*
+ * Check the trigger name in every policy zone that the summary data
+ * says has a hit for the trigger name.
+ * Most of the time there are no eligible zones and the summary data
+ * keeps us from getting this far.
+ * We check the most eligible zone first and so usually check only
+ * one policy zone.
+ */
+ for (rpz_num = 0; zbits != 0; ++rpz_num, zbits >>= 1) {
+ if ((zbits & 1) == 0) {
+ continue;
+ }
+
+ /*
+ * Do not check policy zones that cannot replace a previously
+ * found policy.
+ */
+ rpz = rpzs->zones[rpz_num];
+ if (st->m.policy != DNS_RPZ_POLICY_MISS) {
+ if (st->m.rpz->num < rpz->num) {
+ break;
+ }
+ if (st->m.rpz->num == rpz->num && st->m.type < rpz_type)
+ {
+ break;
+ }
+ }
+
+ /*
+ * Get the next policy zone's record for this trigger name.
+ */
+ result = rpz_get_p_name(client, p_name, rpz, rpz_type,
+ trig_name);
+ if (result != ISC_R_SUCCESS) {
+ continue;
+ }
+ result = rpz_find_p(client, trig_name, qtype, p_name, rpz,
+ rpz_type, &p_zone, &p_db, &p_version,
+ &p_node, rdatasetp, &policy);
+ switch (result) {
+ case DNS_R_NXDOMAIN:
+ /*
+ * Continue after a missing policy record
+ * contrary to the summary data. The summary
+ * data can out of date during races with and among
+ * policy zone updates.
+ */
+ CTRACE(ISC_LOG_ERROR, "rpz_rewrite_name: mismatched "
+ "summary data; "
+ "continuing");
+ continue;
+ case DNS_R_SERVFAIL:
+ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (DNS_R_SERVFAIL);
+ default:
+ /*
+ * With more than one applicable policy, prefer
+ * the earliest configured policy,
+ * client-IP over QNAME over IP over NSDNAME over NSIP,
+ * and the smallest name.
+ * We known st->m.rpz->num >= rpz->num and either
+ * st->m.rpz->num > rpz->num or st->m.type >= rpz_type
+ */
+ if (st->m.policy != DNS_RPZ_POLICY_MISS &&
+ rpz->num == st->m.rpz->num &&
+ (st->m.type < rpz_type ||
+ (st->m.type == rpz_type &&
+ 0 >= dns_name_compare(p_name, st->p_name))))
+ {
+ continue;
+ }
+
+ if (rpz->policy != DNS_RPZ_POLICY_DISABLED) {
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_name: "
+ "rpz_save_p");
+ rpz_save_p(st, rpz, rpz_type, policy, p_name, 0,
+ result, &p_zone, &p_db, &p_node,
+ rdatasetp, p_version);
+ /*
+ * After a hit, higher numbered policy zones
+ * are irrelevant
+ */
+ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
+ return (ISC_R_SUCCESS);
+ }
+ /*
+ * Log DNS_RPZ_POLICY_DISABLED zones
+ * and try the next eligible policy zone.
+ */
+ rpz_log_rewrite(client, true, policy, rpz_type, p_zone,
+ p_name, NULL, rpz_num);
+ break;
+ }
+ }
+
+ rpz_clean(&p_zone, &p_db, &p_node, rdatasetp);
+ return (ISC_R_SUCCESS);
+}
+
+static void
+rpz_rewrite_ns_skip(ns_client_t *client, dns_name_t *nsname,
+ isc_result_t result, int level, const char *str) {
+ dns_rpz_st_t *st;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite_ns_skip");
+
+ st = client->query.rpz_st;
+
+ if (str != NULL) {
+ rpz_log_fail_helper(client, level, nsname, DNS_RPZ_TYPE_NSIP,
+ DNS_RPZ_TYPE_NSDNAME, str, result);
+ }
+ if (st->r.ns_rdataset != NULL &&
+ dns_rdataset_isassociated(st->r.ns_rdataset))
+ {
+ dns_rdataset_disassociate(st->r.ns_rdataset);
+ }
+
+ st->r.label--;
+}
+
+/*
+ * RPZ query result types
+ */
+typedef enum {
+ qresult_type_done = 0,
+ qresult_type_restart = 1,
+ qresult_type_recurse = 2
+} qresult_type_t;
+
+/*
+ * Look for response policy zone QNAME, NSIP, and NSDNAME rewriting.
+ */
+static isc_result_t
+rpz_rewrite(ns_client_t *client, dns_rdatatype_t qtype, isc_result_t qresult,
+ bool resuming, dns_rdataset_t *ordataset, dns_rdataset_t *osigset) {
+ dns_rpz_zones_t *rpzs;
+ dns_rpz_st_t *st;
+ dns_rdataset_t *rdataset;
+ dns_fixedname_t nsnamef;
+ dns_name_t *nsname;
+ qresult_type_t qresult_type;
+ dns_rpz_zbits_t zbits;
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_rpz_have_t have;
+ dns_rpz_popt_t popt;
+ int rpz_ver;
+ unsigned int options;
+#ifdef USE_DNSRPS
+ librpz_emsg_t emsg;
+#endif /* ifdef USE_DNSRPS */
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_rewrite");
+
+ rpzs = client->view->rpzs;
+ st = client->query.rpz_st;
+
+ if (rpzs == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (st != NULL && (st->state & DNS_RPZ_REWRITTEN) != 0) {
+ return (DNS_R_DISALLOWED);
+ }
+ if (RECURSING(client)) {
+ return (DNS_R_DISALLOWED);
+ }
+
+ RWLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ if ((rpzs->p.num_zones == 0 && !rpzs->p.dnsrps_enabled) ||
+ (!RECURSIONOK(client) && rpzs->p.no_rd_ok == 0) ||
+ !rpz_ck_dnssec(client, qresult, ordataset, osigset))
+ {
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+ return (DNS_R_DISALLOWED);
+ }
+ have = rpzs->have;
+ popt = rpzs->p;
+ rpz_ver = rpzs->rpz_ver;
+ RWUNLOCK(&rpzs->search_lock, isc_rwlocktype_read);
+
+#ifndef USE_DNSRPS
+ INSIST(!popt.dnsrps_enabled);
+#endif /* ifndef USE_DNSRPS */
+
+ if (st == NULL) {
+ st = isc_mem_get(client->mctx, sizeof(*st));
+ st->state = 0;
+ st->rpsdb = NULL;
+ }
+ if (st->state == 0) {
+ st->state |= DNS_RPZ_ACTIVE;
+ memset(&st->m, 0, sizeof(st->m));
+ st->m.type = DNS_RPZ_TYPE_BAD;
+ st->m.policy = DNS_RPZ_POLICY_MISS;
+ st->m.ttl = ~0;
+ memset(&st->r, 0, sizeof(st->r));
+ memset(&st->q, 0, sizeof(st->q));
+ st->p_name = dns_fixedname_initname(&st->_p_namef);
+ st->r_name = dns_fixedname_initname(&st->_r_namef);
+ st->fname = dns_fixedname_initname(&st->_fnamef);
+ st->have = have;
+ st->popt = popt;
+ st->rpz_ver = rpz_ver;
+ client->query.rpz_st = st;
+#ifdef USE_DNSRPS
+ if (popt.dnsrps_enabled) {
+ if (st->rpsdb != NULL) {
+ dns_db_detach(&st->rpsdb);
+ }
+ result = dns_dnsrps_rewrite_init(
+ &emsg, st, rpzs, client->query.qname,
+ client->mctx, RECURSIONOK(client));
+ if (result != ISC_R_SUCCESS) {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL,
+ DNS_RPZ_TYPE_QNAME, emsg.c,
+ result);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ return (ISC_R_SUCCESS);
+ }
+ }
+#endif /* ifdef USE_DNSRPS */
+ }
+
+ /*
+ * There is nothing to rewrite if the main query failed.
+ */
+ switch (qresult) {
+ case ISC_R_SUCCESS:
+ case DNS_R_GLUE:
+ case DNS_R_ZONECUT:
+ qresult_type = qresult_type_done;
+ break;
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXRRSET:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_EMPTYWILD:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_COVERINGNSEC:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ qresult_type = qresult_type_restart;
+ break;
+ case DNS_R_DELEGATION:
+ case ISC_R_NOTFOUND:
+ /*
+ * If recursion is on, do only tentative rewriting.
+ * If recursion is off, this the normal and only time we
+ * can rewrite.
+ */
+ if (RECURSIONOK(client)) {
+ qresult_type = qresult_type_recurse;
+ } else {
+ qresult_type = qresult_type_restart;
+ }
+ break;
+ case ISC_R_FAILURE:
+ case ISC_R_TIMEDOUT:
+ case DNS_R_BROKENCHAIN:
+ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL3, NULL,
+ DNS_RPZ_TYPE_QNAME,
+ "stop on qresult in rpz_rewrite()", qresult);
+ return (ISC_R_SUCCESS);
+ default:
+ rpz_log_fail(client, DNS_RPZ_DEBUG_LEVEL1, NULL,
+ DNS_RPZ_TYPE_QNAME,
+ "stop on unrecognized qresult in rpz_rewrite()",
+ qresult);
+ return (ISC_R_SUCCESS);
+ }
+
+ rdataset = NULL;
+
+ if ((st->state & (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME)) !=
+ (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME))
+ {
+ isc_netaddr_t netaddr;
+ dns_rpz_zbits_t allowed;
+
+ if (!st->popt.dnsrps_enabled &&
+ qresult_type == qresult_type_recurse)
+ {
+ /*
+ * This request needs recursion that has not been done.
+ * Get bits for the policy zones that do not need
+ * to wait for the results of recursion.
+ */
+ allowed = st->have.qname_skip_recurse;
+ if (allowed == 0) {
+ return (ISC_R_SUCCESS);
+ }
+ } else {
+ allowed = DNS_RPZ_ALL_ZBITS;
+ }
+
+ /*
+ * Check once for triggers for the client IP address.
+ */
+ if ((st->state & DNS_RPZ_DONE_CLIENT_IP) == 0) {
+ zbits = rpz_get_zbits(client, dns_rdatatype_none,
+ DNS_RPZ_TYPE_CLIENT_IP);
+ zbits &= allowed;
+ if (zbits != 0) {
+ isc_netaddr_fromsockaddr(&netaddr,
+ &client->peeraddr);
+ result = rpz_rewrite_ip(client, &netaddr, qtype,
+ DNS_RPZ_TYPE_CLIENT_IP,
+ zbits, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ }
+ }
+
+ /*
+ * Check triggers for the query name if this is the first time
+ * for the current qname.
+ * There is a first time for each name in a CNAME chain
+ */
+ if ((st->state & DNS_RPZ_DONE_QNAME) == 0) {
+ bool norec = (qresult_type != qresult_type_recurse);
+ result = rpz_rewrite_name(client, client->query.qname,
+ qtype, DNS_RPZ_TYPE_QNAME,
+ allowed, norec, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Check IPv4 addresses in A RRs next.
+ * Reset to the start of the NS names.
+ */
+ st->r.label = dns_name_countlabels(client->query.qname);
+ st->state &= ~(DNS_RPZ_DONE_QNAME_IP |
+ DNS_RPZ_DONE_IPv4);
+ }
+
+ /*
+ * Quit if this was an attempt to find a qname or
+ * client-IP trigger before recursion.
+ * We will be back if no pre-recursion triggers hit.
+ * For example, consider 2 policy zones, both with qname and
+ * IP address triggers. If the qname misses the 1st zone,
+ * then we cannot know whether a hit for the qname in the
+ * 2nd zone matters until after recursing to get the A RRs and
+ * testing them in the first zone.
+ * Do not bother saving the work from this attempt,
+ * because recursion is so slow.
+ */
+ if (qresult_type == qresult_type_recurse) {
+ goto cleanup;
+ }
+
+ /*
+ * DNS_RPZ_DONE_QNAME but not DNS_RPZ_DONE_CLIENT_IP
+ * is reset at the end of dealing with each CNAME.
+ */
+ st->state |= (DNS_RPZ_DONE_CLIENT_IP | DNS_RPZ_DONE_QNAME);
+ }
+
+ /*
+ * Check known IP addresses for the query name if the database lookup
+ * resulted in some addresses (qresult_type == qresult_type_done)
+ * and if we have not already checked them.
+ * Any recursion required for the query has already happened.
+ * Do not check addresses that will not be in the ANSWER section.
+ */
+ if ((st->state & DNS_RPZ_DONE_QNAME_IP) == 0 &&
+ qresult_type == qresult_type_done &&
+ rpz_get_zbits(client, qtype, DNS_RPZ_TYPE_IP) != 0)
+ {
+ result = rpz_rewrite_ip_rrsets(client, client->query.qname,
+ qtype, DNS_RPZ_TYPE_IP,
+ &rdataset, resuming);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ /*
+ * We are finished checking the IP addresses for the qname.
+ * Start with IPv4 if we will check NS IP addresses.
+ */
+ st->state |= DNS_RPZ_DONE_QNAME_IP;
+ st->state &= ~DNS_RPZ_DONE_IPv4;
+ }
+
+ /*
+ * Stop looking for rules if there are none of the other kinds
+ * that could override what we already have.
+ */
+ if (rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSDNAME) ==
+ 0 &&
+ rpz_get_zbits(client, dns_rdatatype_any, DNS_RPZ_TYPE_NSIP) == 0)
+ {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ dns_fixedname_init(&nsnamef);
+ dns_name_clone(client->query.qname, dns_fixedname_name(&nsnamef));
+ options = client->query.dboptions | DNS_DBFIND_GLUEOK;
+ while (st->r.label > st->popt.min_ns_labels) {
+ bool was_glue = false;
+ /*
+ * Get NS rrset for each domain in the current qname.
+ */
+ if (st->r.label == dns_name_countlabels(client->query.qname)) {
+ nsname = client->query.qname;
+ } else {
+ nsname = dns_fixedname_name(&nsnamef);
+ dns_name_split(client->query.qname, st->r.label, NULL,
+ nsname);
+ }
+ if (st->r.ns_rdataset == NULL ||
+ !dns_rdataset_isassociated(st->r.ns_rdataset))
+ {
+ dns_db_t *db = NULL;
+ result = rpz_rrset_find(client, nsname,
+ dns_rdatatype_ns, options,
+ DNS_RPZ_TYPE_NSDNAME, &db, NULL,
+ &st->r.ns_rdataset, resuming);
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (st->m.policy == DNS_RPZ_POLICY_ERROR) {
+ goto cleanup;
+ }
+ switch (result) {
+ case DNS_R_GLUE:
+ was_glue = true;
+ FALLTHROUGH;
+ case ISC_R_SUCCESS:
+ result = dns_rdataset_first(st->r.ns_rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ st->state &= ~(DNS_RPZ_DONE_NSDNAME |
+ DNS_RPZ_DONE_IPv4);
+ break;
+ case DNS_R_DELEGATION:
+ case DNS_R_DUPLICATE:
+ case DNS_R_DROP:
+ goto cleanup;
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXRRSET:
+ case DNS_R_EMPTYWILD:
+ case DNS_R_NXDOMAIN:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ case ISC_R_NOTFOUND:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ rpz_rewrite_ns_skip(client, nsname, result, 0,
+ NULL);
+ continue;
+ case ISC_R_TIMEDOUT:
+ case DNS_R_BROKENCHAIN:
+ case ISC_R_FAILURE:
+ rpz_rewrite_ns_skip(client, nsname, result,
+ DNS_RPZ_DEBUG_LEVEL3,
+ " NS rpz_rrset_find()");
+ continue;
+ default:
+ rpz_rewrite_ns_skip(client, nsname, result,
+ DNS_RPZ_INFO_LEVEL,
+ " unrecognized NS"
+ " rpz_rrset_find()");
+ continue;
+ }
+ }
+
+ /*
+ * Check all NS names.
+ */
+ do {
+ dns_rdata_ns_t ns;
+ dns_rdata_t nsrdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(st->r.ns_rdataset, &nsrdata);
+ result = dns_rdata_tostruct(&nsrdata, &ns, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&nsrdata);
+
+ /*
+ * Do nothing about "NS ."
+ */
+ if (dns_name_equal(&ns.name, dns_rootname)) {
+ dns_rdata_freestruct(&ns);
+ result = dns_rdataset_next(st->r.ns_rdataset);
+ continue;
+ }
+ /*
+ * Check this NS name if we did not handle it
+ * during a previous recursion.
+ */
+ if ((st->state & DNS_RPZ_DONE_NSDNAME) == 0) {
+ result = rpz_rewrite_name(
+ client, &ns.name, qtype,
+ DNS_RPZ_TYPE_NSDNAME, DNS_RPZ_ALL_ZBITS,
+ true, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_freestruct(&ns);
+ goto cleanup;
+ }
+ st->state |= DNS_RPZ_DONE_NSDNAME;
+ }
+ /*
+ * Check all IP addresses for this NS name.
+ */
+ result = rpz_rewrite_ip_rrsets(client, &ns.name, qtype,
+ DNS_RPZ_TYPE_NSIP,
+ &rdataset, resuming);
+ dns_rdata_freestruct(&ns);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ st->state &= ~(DNS_RPZ_DONE_NSDNAME |
+ DNS_RPZ_DONE_IPv4);
+ result = dns_rdataset_next(st->r.ns_rdataset);
+ } while (result == ISC_R_SUCCESS);
+ dns_rdataset_disassociate(st->r.ns_rdataset);
+
+ /*
+ * If we just checked a glue NS RRset retry without allowing
+ * glue responses, otherwise setup for the next name.
+ */
+ if (was_glue) {
+ options = client->query.dboptions;
+ } else {
+ options = client->query.dboptions | DNS_DBFIND_GLUEOK;
+ st->r.label--;
+ }
+
+ if (rpz_get_zbits(client, dns_rdatatype_any,
+ DNS_RPZ_TYPE_NSDNAME) == 0 &&
+ rpz_get_zbits(client, dns_rdatatype_any,
+ DNS_RPZ_TYPE_NSIP) == 0)
+ {
+ break;
+ }
+ }
+
+ /*
+ * Use the best hit, if any.
+ */
+ result = ISC_R_SUCCESS;
+
+cleanup:
+#ifdef USE_DNSRPS
+ if (st->popt.dnsrps_enabled && st->m.policy != DNS_RPZ_POLICY_ERROR &&
+ !dnsrps_set_p(&emsg, client, st, qtype, &rdataset,
+ (qresult_type != qresult_type_recurse)))
+ {
+ rpz_log_fail(client, DNS_RPZ_ERROR_LEVEL, NULL,
+ DNS_RPZ_TYPE_BAD, emsg.c, DNS_R_SERVFAIL);
+ st->m.policy = DNS_RPZ_POLICY_ERROR;
+ }
+#endif /* ifdef USE_DNSRPS */
+ if (st->m.policy != DNS_RPZ_POLICY_MISS &&
+ st->m.policy != DNS_RPZ_POLICY_ERROR &&
+ st->m.rpz->policy != DNS_RPZ_POLICY_GIVEN)
+ {
+ st->m.policy = st->m.rpz->policy;
+ }
+ if (st->m.policy == DNS_RPZ_POLICY_MISS ||
+ st->m.policy == DNS_RPZ_POLICY_PASSTHRU ||
+ st->m.policy == DNS_RPZ_POLICY_ERROR)
+ {
+ if (st->m.policy == DNS_RPZ_POLICY_PASSTHRU &&
+ result != DNS_R_DELEGATION)
+ {
+ rpz_log_rewrite(client, false, st->m.policy, st->m.type,
+ st->m.zone, st->p_name, NULL,
+ st->m.rpz->num);
+ }
+ rpz_match_clear(st);
+ }
+ if (st->m.policy == DNS_RPZ_POLICY_ERROR) {
+ CTRACE(ISC_LOG_ERROR, "SERVFAIL due to RPZ policy");
+ st->m.type = DNS_RPZ_TYPE_BAD;
+ result = DNS_R_SERVFAIL;
+ }
+ ns_client_putrdataset(client, &rdataset);
+ if ((st->state & DNS_RPZ_RECURSING) == 0) {
+ rpz_clean(NULL, &st->r.db, NULL, &st->r.ns_rdataset);
+ }
+
+ return (result);
+}
+
+/*
+ * See if response policy zone rewriting is allowed by a lack of interest
+ * by the client in DNSSEC or a lack of signatures.
+ */
+static bool
+rpz_ck_dnssec(ns_client_t *client, isc_result_t qresult,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
+ dns_fixedname_t fixed;
+ dns_name_t *found;
+ dns_rdataset_t trdataset;
+ dns_rdatatype_t type;
+ isc_result_t result;
+
+ CTRACE(ISC_LOG_DEBUG(3), "rpz_ck_dnssec");
+
+ if (client->view->rpzs->p.break_dnssec || !WANTDNSSEC(client)) {
+ return (true);
+ }
+
+ /*
+ * We do not know if there are signatures if we have not recursed
+ * for them.
+ */
+ if (qresult == DNS_R_DELEGATION || qresult == ISC_R_NOTFOUND) {
+ return (false);
+ }
+
+ if (sigrdataset == NULL) {
+ return (true);
+ }
+ if (dns_rdataset_isassociated(sigrdataset)) {
+ return (false);
+ }
+
+ /*
+ * We are happy to rewrite nothing.
+ */
+ if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) {
+ return (true);
+ }
+ /*
+ * Do not rewrite if there is any sign of signatures.
+ */
+ if (rdataset->type == dns_rdatatype_nsec ||
+ rdataset->type == dns_rdatatype_nsec3 ||
+ rdataset->type == dns_rdatatype_rrsig)
+ {
+ return (false);
+ }
+
+ /*
+ * Look for a signature in a negative cache rdataset.
+ */
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) == 0) {
+ return (true);
+ }
+ found = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&trdataset);
+ for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_ncache_current(rdataset, found, &trdataset);
+ type = trdataset.type;
+ dns_rdataset_disassociate(&trdataset);
+ if (type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3 ||
+ type == dns_rdatatype_rrsig)
+ {
+ return (false);
+ }
+ }
+ return (true);
+}
+
+/*
+ * Extract a network address from the RDATA of an A or AAAA
+ * record.
+ *
+ * Returns:
+ * ISC_R_SUCCESS
+ * ISC_R_NOTIMPLEMENTED The rdata is not a known address type.
+ */
+static isc_result_t
+rdata_tonetaddr(const dns_rdata_t *rdata, isc_netaddr_t *netaddr) {
+ struct in_addr ina;
+ struct in6_addr in6a;
+
+ switch (rdata->type) {
+ case dns_rdatatype_a:
+ INSIST(rdata->length == 4);
+ memmove(&ina.s_addr, rdata->data, 4);
+ isc_netaddr_fromin(netaddr, &ina);
+ return (ISC_R_SUCCESS);
+ case dns_rdatatype_aaaa:
+ INSIST(rdata->length == 16);
+ memmove(in6a.s6_addr, rdata->data, 16);
+ isc_netaddr_fromin6(netaddr, &in6a);
+ return (ISC_R_SUCCESS);
+ default:
+ return (ISC_R_NOTIMPLEMENTED);
+ }
+}
+
+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 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)
+};
+
+static unsigned char prisoner_data[] = "\010prisoner\004iana\003org";
+static unsigned char hostmaster_data[] = "\012hostmaster\014root-"
+ "servers\003org";
+
+static unsigned char prisoner_offsets[] = { 0, 9, 14, 18 };
+static unsigned char hostmaster_offsets[] = { 0, 11, 24, 28 };
+
+static dns_name_t const prisoner = DNS_NAME_INITABSOLUTE(prisoner_data,
+ prisoner_offsets);
+static dns_name_t const hostmaster = DNS_NAME_INITABSOLUTE(hostmaster_data,
+ hostmaster_offsets);
+
+static void
+warn_rfc1918(ns_client_t *client, dns_name_t *fname, dns_rdataset_t *rdataset) {
+ unsigned int i;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_soa_t soa;
+ dns_rdataset_t found;
+ isc_result_t result;
+
+ for (i = 0; i < (sizeof(rfc1918names) / sizeof(*rfc1918names)); i++) {
+ if (dns_name_issubdomain(fname, &rfc1918names[i])) {
+ dns_rdataset_init(&found);
+ result = dns_ncache_getrdataset(
+ rdataset, &rfc1918names[i], dns_rdatatype_soa,
+ &found);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ result = dns_rdataset_first(&found);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(&found, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (dns_name_equal(&soa.origin, &prisoner) &&
+ dns_name_equal(&soa.contact, &hostmaster))
+ {
+ char buf[DNS_NAME_FORMATSIZE];
+ dns_name_format(fname, buf, sizeof(buf));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY,
+ ISC_LOG_WARNING,
+ "RFC 1918 response from "
+ "Internet for %s",
+ buf);
+ }
+ dns_rdataset_disassociate(&found);
+ return;
+ }
+ }
+}
+
+static void
+query_findclosestnsec3(dns_name_t *qname, dns_db_t *db,
+ dns_dbversion_t *version, ns_client_t *client,
+ dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
+ dns_name_t *fname, bool exact, dns_name_t *found) {
+ unsigned char salt[256];
+ size_t salt_length;
+ uint16_t iterations;
+ isc_result_t result;
+ unsigned int dboptions;
+ dns_fixedname_t fixed;
+ dns_hash_t hash;
+ dns_name_t name;
+ unsigned int skip = 0, labels;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ bool optout;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ salt_length = sizeof(salt);
+ result = dns_db_getnsec3parameters(db, version, &hash, NULL,
+ &iterations, salt, &salt_length);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ dns_name_init(&name, NULL);
+ dns_name_clone(qname, &name);
+ labels = dns_name_countlabels(&name);
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Map unknown algorithm to known value.
+ */
+ if (hash == DNS_NSEC3_UNKNOWNALG) {
+ hash = 1;
+ }
+
+again:
+ dns_fixedname_init(&fixed);
+ result = dns_nsec3_hashname(&fixed, NULL, NULL, &name,
+ dns_db_origin(db), hash, iterations, salt,
+ salt_length);
+ if (result != ISC_R_SUCCESS) {
+ return;
+ }
+
+ dboptions = client->query.dboptions | DNS_DBFIND_FORCENSEC3;
+ result = dns_db_findext(db, dns_fixedname_name(&fixed), version,
+ dns_rdatatype_nsec3, dboptions, client->now,
+ NULL, fname, &cm, &ci, rdataset, sigrdataset);
+
+ if (result == DNS_R_NXDOMAIN) {
+ if (!dns_rdataset_isassociated(rdataset)) {
+ return;
+ }
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+ optout = ((nsec3.flags & DNS_NSEC3FLAG_OPTOUT) != 0);
+ if (found != NULL && optout &&
+ dns_name_issubdomain(&name, dns_db_origin(db)))
+ {
+ dns_rdataset_disassociate(rdataset);
+ if (dns_rdataset_isassociated(sigrdataset)) {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ skip++;
+ dns_name_getlabelsequence(qname, skip, labels - skip,
+ &name);
+ ns_client_log(client, DNS_LOGCATEGORY_DNSSEC,
+ NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(3),
+ "looking for closest provable encloser");
+ goto again;
+ }
+ if (exact) {
+ ns_client_log(client, DNS_LOGCATEGORY_DNSSEC,
+ NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+ "expected a exact match NSEC3, got "
+ "a covering record");
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ return;
+ } else if (!exact) {
+ ns_client_log(client, DNS_LOGCATEGORY_DNSSEC,
+ NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+ "expected covering NSEC3, got an exact match");
+ }
+ if (found == qname) {
+ if (skip != 0U) {
+ dns_name_getlabelsequence(qname, skip, labels - skip,
+ found);
+ }
+ } else if (found != NULL) {
+ dns_name_copy(&name, found);
+ }
+ return;
+}
+
+static uint32_t
+dns64_ttl(dns_db_t *db, dns_dbversion_t *version) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_soa_t soa;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ uint32_t ttl = UINT32_MAX;
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ result = dns_db_findrdataset(db, node, version, dns_rdatatype_soa, 0, 0,
+ &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_rdataset_first(&rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ ttl = ISC_MIN(rdataset.ttl, soa.minimum);
+
+cleanup:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ return (ttl);
+}
+
+static bool
+dns64_aaaaok(ns_client_t *client, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ isc_netaddr_t netaddr;
+ dns_aclenv_t *env = client->manager->aclenv;
+ dns_dns64_t *dns64 = ISC_LIST_HEAD(client->view->dns64);
+ unsigned int flags = 0;
+ unsigned int i, count;
+ bool *aaaaok;
+
+ INSIST(client->query.dns64_aaaaok == NULL);
+ INSIST(client->query.dns64_aaaaoklen == 0);
+ INSIST(client->query.dns64_aaaa == NULL);
+ INSIST(client->query.dns64_sigaaaa == NULL);
+
+ if (dns64 == NULL) {
+ return (true);
+ }
+
+ if (RECURSIONOK(client)) {
+ flags |= DNS_DNS64_RECURSIVE;
+ }
+
+ if (WANTDNSSEC(client) && sigrdataset != NULL &&
+ dns_rdataset_isassociated(sigrdataset))
+ {
+ flags |= DNS_DNS64_DNSSEC;
+ }
+
+ count = dns_rdataset_count(rdataset);
+ aaaaok = isc_mem_get(client->mctx, sizeof(bool) * count);
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ if (dns_dns64_aaaaok(dns64, &netaddr, client->signer, env, flags,
+ rdataset, aaaaok, count))
+ {
+ for (i = 0; i < count; i++) {
+ if (aaaaok != NULL && !aaaaok[i]) {
+ SAVE(client->query.dns64_aaaaok, aaaaok);
+ client->query.dns64_aaaaoklen = count;
+ break;
+ }
+ }
+ if (aaaaok != NULL) {
+ isc_mem_put(client->mctx, aaaaok, sizeof(bool) * count);
+ }
+ return (true);
+ }
+ if (aaaaok != NULL) {
+ isc_mem_put(client->mctx, aaaaok, sizeof(bool) * count);
+ }
+ return (false);
+}
+
+/*
+ * Look for the name and type in the redirection zone. If found update
+ * the arguments as appropriate. Return true if a update was
+ * performed.
+ *
+ * Only perform the update if the client is in the allow query acl and
+ * returning the update would not cause a DNSSEC validation failure.
+ */
+static isc_result_t
+redirect(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset,
+ dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp,
+ dns_rdatatype_t qtype) {
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *found;
+ dns_rdataset_t trdataset;
+ isc_result_t result;
+ dns_rdatatype_t type;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ ns_dbversion_t *dbversion;
+
+ CTRACE(ISC_LOG_DEBUG(3), "redirect");
+
+ if (client->view->redirect == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ found = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&trdataset);
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+ dns_clientinfo_setecs(&ci, &client->ecs);
+
+ if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp))
+ {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) {
+ if (rdataset->trust == dns_trust_secure) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (rdataset->trust == dns_trust_ultimate &&
+ (rdataset->type == dns_rdatatype_nsec ||
+ rdataset->type == dns_rdatatype_nsec3))
+ {
+ return (ISC_R_NOTFOUND);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_ncache_current(rdataset, found, &trdataset);
+ type = trdataset.type;
+ dns_rdataset_disassociate(&trdataset);
+ if (type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_nsec3 ||
+ type == dns_rdatatype_rrsig)
+ {
+ return (ISC_R_NOTFOUND);
+ }
+ }
+ }
+ }
+
+ result = ns_client_checkaclsilent(
+ client, NULL, dns_zone_getqueryacl(client->view->redirect),
+ true);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ result = dns_zone_getdb(client->view->redirect, &db);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ dbversion = ns_client_findversion(client, db);
+ if (dbversion == NULL) {
+ dns_db_detach(&db);
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Lookup the requested data in the redirect zone.
+ */
+ result = dns_db_findext(db, client->query.qname, dbversion->version,
+ qtype, DNS_DBFIND_NOZONECUT, client->now, &node,
+ found, &cm, &ci, &trdataset, NULL);
+ if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ goto nxrrset;
+ } else if (result != ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+ return (ISC_R_NOTFOUND);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "redirect: found data: done");
+ dns_name_copy(found, name);
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_clone(&trdataset, rdataset);
+ dns_rdataset_disassociate(&trdataset);
+ }
+nxrrset:
+ if (*nodep != NULL) {
+ dns_db_detachnode(*dbp, nodep);
+ }
+ dns_db_detach(dbp);
+ dns_db_attachnode(db, node, nodep);
+ dns_db_attach(db, dbp);
+ dns_db_detachnode(db, &node);
+ dns_db_detach(&db);
+ *versionp = dbversion->version;
+
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+
+ return (result);
+}
+
+static isc_result_t
+redirect2(ns_client_t *client, dns_name_t *name, dns_rdataset_t *rdataset,
+ dns_dbnode_t **nodep, dns_db_t **dbp, dns_dbversion_t **versionp,
+ dns_rdatatype_t qtype, bool *is_zonep) {
+ dns_db_t *db = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fixed;
+ dns_fixedname_t fixedredirect;
+ dns_name_t *found, *redirectname;
+ dns_rdataset_t trdataset;
+ isc_result_t result;
+ dns_rdatatype_t type;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_dbversion_t *version = NULL;
+ dns_zone_t *zone = NULL;
+ bool is_zone;
+ unsigned int labels;
+ unsigned int options;
+
+ CTRACE(ISC_LOG_DEBUG(3), "redirect2");
+
+ if (client->view->redirectzone == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (dns_name_issubdomain(name, client->view->redirectzone)) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ found = dns_fixedname_initname(&fixed);
+ dns_rdataset_init(&trdataset);
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+ dns_clientinfo_setecs(&ci, &client->ecs);
+
+ if (WANTDNSSEC(client) && dns_db_iszone(*dbp) && dns_db_issecure(*dbp))
+ {
+ return (ISC_R_NOTFOUND);
+ }
+
+ if (WANTDNSSEC(client) && dns_rdataset_isassociated(rdataset)) {
+ if (rdataset->trust == dns_trust_secure) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (rdataset->trust == dns_trust_ultimate &&
+ (rdataset->type == dns_rdatatype_nsec ||
+ rdataset->type == dns_rdatatype_nsec3))
+ {
+ return (ISC_R_NOTFOUND);
+ }
+ if ((rdataset->attributes & DNS_RDATASETATTR_NEGATIVE) != 0) {
+ for (result = dns_rdataset_first(rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(rdataset))
+ {
+ dns_ncache_current(rdataset, found, &trdataset);
+ type = trdataset.type;
+ dns_rdataset_disassociate(&trdataset);
+ if (type == dns_rdatatype_nsec ||
+ type == dns_rdatatype_nsec3 ||
+ type == dns_rdatatype_rrsig)
+ {
+ return (ISC_R_NOTFOUND);
+ }
+ }
+ }
+ }
+
+ redirectname = dns_fixedname_initname(&fixedredirect);
+ labels = dns_name_countlabels(client->query.qname);
+ if (labels > 1U) {
+ dns_name_t prefix;
+
+ dns_name_init(&prefix, NULL);
+ dns_name_getlabelsequence(client->query.qname, 0, labels - 1,
+ &prefix);
+ result = dns_name_concatenate(&prefix,
+ client->view->redirectzone,
+ redirectname, NULL);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+ } else {
+ dns_name_copy(redirectname, client->view->redirectzone);
+ }
+
+ options = 0;
+ result = query_getdb(client, redirectname, qtype, options, &zone, &db,
+ &version, &is_zone);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_NOTFOUND);
+ }
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+
+ /*
+ * Lookup the requested data in the redirect zone.
+ */
+ result = dns_db_findext(db, redirectname, version, qtype, 0,
+ client->now, &node, found, &cm, &ci, &trdataset,
+ NULL);
+ if (result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ goto nxrrset;
+ } else if (result == ISC_R_NOTFOUND || result == DNS_R_DELEGATION) {
+ /*
+ * Cleanup.
+ */
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+ /*
+ * Don't loop forever if the lookup failed last time.
+ */
+ if (!REDIRECT(client)) {
+ result = ns_query_recurse(client, qtype, redirectname,
+ NULL, NULL, true);
+ if (result == ISC_R_SUCCESS) {
+ client->query.attributes |=
+ NS_QUERYATTR_RECURSING;
+ client->query.attributes |=
+ NS_QUERYATTR_REDIRECT;
+ return (DNS_R_CONTINUE);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+ } else if (result != ISC_R_SUCCESS) {
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_disassociate(&trdataset);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+ return (ISC_R_NOTFOUND);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "redirect2: found data: done");
+ /*
+ * Adjust the found name to not include the redirectzone suffix.
+ */
+ dns_name_split(found, dns_name_countlabels(client->view->redirectzone),
+ found, NULL);
+ /*
+ * Make the name absolute.
+ */
+ result = dns_name_concatenate(found, dns_rootname, found, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_name_copy(found, name);
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (dns_rdataset_isassociated(&trdataset)) {
+ dns_rdataset_clone(&trdataset, rdataset);
+ dns_rdataset_disassociate(&trdataset);
+ }
+nxrrset:
+ if (*nodep != NULL) {
+ dns_db_detachnode(*dbp, nodep);
+ }
+ dns_db_detach(dbp);
+ dns_db_attachnode(db, node, nodep);
+ dns_db_attach(db, dbp);
+ dns_db_detachnode(db, &node);
+ dns_db_detach(&db);
+ *is_zonep = is_zone;
+ *versionp = version;
+
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+
+ return (result);
+}
+
+/*%
+ * Initialize query context 'qctx'. Run by query_setup() when
+ * first handling a client query, and by query_resume() when
+ * returning from recursion.
+ *
+ * Whenever this function is called, qctx_destroy() must be called
+ * when leaving the scope or freeing the qctx.
+ */
+static void
+qctx_init(ns_client_t *client, dns_fetchevent_t **eventp, dns_rdatatype_t qtype,
+ query_ctx_t *qctx) {
+ REQUIRE(qctx != NULL);
+ REQUIRE(client != NULL);
+
+ memset(qctx, 0, sizeof(*qctx));
+
+ /* Set this first so CCTRACE will work */
+ qctx->client = client;
+
+ dns_view_attach(client->view, &qctx->view);
+
+ CCTRACE(ISC_LOG_DEBUG(3), "qctx_init");
+
+ if (eventp != NULL) {
+ qctx->event = *eventp;
+ *eventp = NULL;
+ } else {
+ qctx->event = NULL;
+ }
+ qctx->qtype = qctx->type = qtype;
+ qctx->result = ISC_R_SUCCESS;
+ qctx->findcoveringnsec = qctx->view->synthfromdnssec;
+
+ /*
+ * If it's an RRSIG or SIG query, we'll iterate the node.
+ */
+ if (qctx->qtype == dns_rdatatype_rrsig ||
+ qctx->qtype == dns_rdatatype_sig)
+ {
+ qctx->type = dns_rdatatype_any;
+ }
+
+ CALL_HOOK_NORETURN(NS_QUERY_QCTX_INITIALIZED, qctx);
+}
+
+/*
+ * Make 'dst' and exact copy of 'src', with exception of the
+ * option field, which is reset to zero.
+ * This function also attaches dst's view and db to the src's
+ * view and cachedb.
+ */
+static void
+qctx_copy(const query_ctx_t *qctx, query_ctx_t *dst) {
+ REQUIRE(qctx != NULL);
+ REQUIRE(dst != NULL);
+
+ memmove(dst, qctx, sizeof(*dst));
+ dst->view = NULL;
+ dst->db = NULL;
+ dst->options = 0;
+ dns_view_attach(qctx->view, &dst->view);
+ dns_db_attach(qctx->view->cachedb, &dst->db);
+ CCTRACE(ISC_LOG_DEBUG(3), "qctx_copy");
+}
+
+/*%
+ * Clean up and disassociate the rdataset and node pointers in qctx.
+ */
+static void
+qctx_clean(query_ctx_t *qctx) {
+ if (qctx->rdataset != NULL && dns_rdataset_isassociated(qctx->rdataset))
+ {
+ dns_rdataset_disassociate(qctx->rdataset);
+ }
+ if (qctx->sigrdataset != NULL &&
+ dns_rdataset_isassociated(qctx->sigrdataset))
+ {
+ dns_rdataset_disassociate(qctx->sigrdataset);
+ }
+ if (qctx->db != NULL && qctx->node != NULL) {
+ dns_db_detachnode(qctx->db, &qctx->node);
+ }
+}
+
+/*%
+ * Free any allocated memory associated with qctx.
+ */
+static void
+qctx_freedata(query_ctx_t *qctx) {
+ if (qctx->rdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ }
+
+ if (qctx->sigrdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->sigrdataset);
+ }
+
+ if (qctx->fname != NULL) {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ if (qctx->db != NULL) {
+ INSIST(qctx->node == NULL);
+ dns_db_detach(&qctx->db);
+ }
+
+ if (qctx->zone != NULL) {
+ dns_zone_detach(&qctx->zone);
+ }
+
+ if (qctx->zdb != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->zsigrdataset);
+ ns_client_putrdataset(qctx->client, &qctx->zrdataset);
+ ns_client_releasename(qctx->client, &qctx->zfname);
+ dns_db_detachnode(qctx->zdb, &qctx->znode);
+ dns_db_detach(&qctx->zdb);
+ }
+
+ if (qctx->event != NULL && !qctx->client->nodetach) {
+ free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event),
+ &qctx->event);
+ }
+}
+
+static void
+qctx_destroy(query_ctx_t *qctx) {
+ CALL_HOOK_NORETURN(NS_QUERY_QCTX_DESTROYED, qctx);
+
+ dns_view_detach(&qctx->view);
+}
+
+/*
+ * Call SAVE but set 'a' to NULL first so as not to assert.
+ */
+#define INITANDSAVE(a, b) \
+ do { \
+ a = NULL; \
+ SAVE(a, b); \
+ } while (0)
+
+/*
+ * "save" qctx data from 'src' to 'tgt'.
+ * It essentially moves ownership of the data from src to tgt, so the former
+ * becomes unusable except for final cleanup (such as by qctx_destroy).
+ * Note: this function doesn't attach to the client's handle. It's the caller's
+ * responsibility to do it if it's necessary.
+ */
+static void
+qctx_save(query_ctx_t *src, query_ctx_t *tgt) {
+ /* First copy all fields in a straightforward way */
+ *tgt = *src;
+
+ /* Then "move" pointers (except client and view) */
+ INITANDSAVE(tgt->dbuf, src->dbuf);
+ INITANDSAVE(tgt->fname, src->fname);
+ INITANDSAVE(tgt->tname, src->tname);
+ INITANDSAVE(tgt->rdataset, src->rdataset);
+ INITANDSAVE(tgt->sigrdataset, src->sigrdataset);
+ INITANDSAVE(tgt->noqname, src->noqname);
+ INITANDSAVE(tgt->event, src->event);
+ INITANDSAVE(tgt->db, src->db);
+ INITANDSAVE(tgt->version, src->version);
+ INITANDSAVE(tgt->node, src->node);
+ INITANDSAVE(tgt->zdb, src->zdb);
+ INITANDSAVE(tgt->znode, src->znode);
+ INITANDSAVE(tgt->zfname, src->zfname);
+ INITANDSAVE(tgt->zversion, src->zversion);
+ INITANDSAVE(tgt->zrdataset, src->zrdataset);
+ INITANDSAVE(tgt->zsigrdataset, src->zsigrdataset);
+ INITANDSAVE(tgt->rpz_st, src->rpz_st);
+ INITANDSAVE(tgt->zone, src->zone);
+
+ /* View has to stay in 'src' for qctx_destroy. */
+ tgt->view = NULL;
+ dns_view_attach(src->view, &tgt->view);
+}
+
+/*%
+ * Log detailed information about the query immediately after
+ * the client request or a return from recursion.
+ */
+static void
+query_trace(query_ctx_t *qctx) {
+#ifdef WANT_QUERYTRACE
+ char mbuf[2 * DNS_NAME_FORMATSIZE];
+ char qbuf[DNS_NAME_FORMATSIZE];
+
+ if (qctx->client->query.origqname != NULL) {
+ dns_name_format(qctx->client->query.origqname, qbuf,
+ sizeof(qbuf));
+ } else {
+ snprintf(qbuf, sizeof(qbuf), "<unset>");
+ }
+
+ snprintf(mbuf, sizeof(mbuf) - 1,
+ "client attr:0x%x, query attr:0x%X, restarts:%u, "
+ "origqname:%s, timer:%d, authdb:%d, referral:%d",
+ qctx->client->attributes, qctx->client->query.attributes,
+ qctx->client->query.restarts, qbuf,
+ (int)qctx->client->query.timerset,
+ (int)qctx->client->query.authdbset,
+ (int)qctx->client->query.isreferral);
+ CCTRACE(ISC_LOG_DEBUG(3), mbuf);
+#else /* ifdef WANT_QUERYTRACE */
+ UNUSED(qctx);
+#endif /* ifdef WANT_QUERYTRACE */
+}
+
+/*
+ * Set up query processing for the current query of 'client'.
+ * Calls qctx_init() to initialize a query context, checks
+ * the SERVFAIL cache, then hands off processing to ns__query_start().
+ *
+ * This is called only from ns_query_start(), to begin a query
+ * for the first time. Restarting an existing query (for
+ * instance, to handle CNAME lookups), is done by calling
+ * ns__query_start() again with the same query context. Resuming from
+ * recursion is handled by query_resume().
+ */
+static isc_result_t
+query_setup(ns_client_t *client, dns_rdatatype_t qtype) {
+ isc_result_t result = ISC_R_UNSET;
+ query_ctx_t qctx;
+
+ qctx_init(client, NULL, qtype, &qctx);
+ query_trace(&qctx);
+
+ CALL_HOOK(NS_QUERY_SETUP, &qctx);
+
+ /*
+ * Check SERVFAIL cache
+ */
+ result = ns__query_sfcache(&qctx);
+ if (result != ISC_R_COMPLETE) {
+ qctx_destroy(&qctx);
+ return (result);
+ }
+
+ result = ns__query_start(&qctx);
+
+cleanup:
+ qctx_destroy(&qctx);
+ return (result);
+}
+
+static bool
+get_root_key_sentinel_id(query_ctx_t *qctx, const char *ndata) {
+ unsigned int v = 0;
+ int i;
+
+ for (i = 0; i < 5; i++) {
+ if (!isdigit((unsigned char)ndata[i])) {
+ return (false);
+ }
+ v *= 10;
+ v += ndata[i] - '0';
+ }
+ if (v > 65535U) {
+ return (false);
+ }
+ qctx->client->query.root_key_sentinel_keyid = v;
+ return (true);
+}
+
+/*%
+ * Find out if the query is for a root key sentinel and if so, record the type
+ * of root key sentinel query and the key id that is being checked for.
+ *
+ * The code is assuming a zero padded decimal field of width 5.
+ */
+static void
+root_key_sentinel_detect(query_ctx_t *qctx) {
+ const char *ndata = (const char *)qctx->client->query.qname->ndata;
+
+ if (qctx->client->query.qname->length > 30 && ndata[0] == 29 &&
+ strncasecmp(ndata + 1, "root-key-sentinel-is-ta-", 24) == 0)
+ {
+ if (!get_root_key_sentinel_id(qctx, ndata + 25)) {
+ return;
+ }
+ qctx->client->query.root_key_sentinel_is_ta = true;
+ /*
+ * Simplify processing by disabling aggressive
+ * negative caching.
+ */
+ qctx->findcoveringnsec = false;
+ ns_client_log(qctx->client, NS_LOGCATEGORY_TAT,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "root-key-sentinel-is-ta query label found");
+ } else if (qctx->client->query.qname->length > 31 && ndata[0] == 30 &&
+ strncasecmp(ndata + 1, "root-key-sentinel-not-ta-", 25) == 0)
+ {
+ if (!get_root_key_sentinel_id(qctx, ndata + 26)) {
+ return;
+ }
+ qctx->client->query.root_key_sentinel_not_ta = true;
+ /*
+ * Simplify processing by disabling aggressive
+ * negative caching.
+ */
+ qctx->findcoveringnsec = false;
+ ns_client_log(qctx->client, NS_LOGCATEGORY_TAT,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "root-key-sentinel-not-ta query label found");
+ }
+}
+
+/*%
+ * Starting point for a client query or a chaining query.
+ *
+ * Called first by query_setup(), and then again as often as needed to
+ * follow a CNAME chain. Determines which authoritative database to
+ * search, then hands off processing to query_lookup().
+ */
+isc_result_t
+ns__query_start(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ CCTRACE(ISC_LOG_DEBUG(3), "ns__query_start");
+ qctx->want_restart = false;
+ qctx->authoritative = false;
+ qctx->version = NULL;
+ qctx->zversion = NULL;
+ qctx->need_wildcardproof = false;
+ qctx->rpz = false;
+
+ CALL_HOOK(NS_QUERY_START_BEGIN, qctx);
+
+ /*
+ * If we require a server cookie then send back BADCOOKIE
+ * before we have done too much work.
+ */
+ if (!TCP(qctx->client) && qctx->view->requireservercookie &&
+ WANTCOOKIE(qctx->client) && !HAVECOOKIE(qctx->client))
+ {
+ qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA;
+ qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
+ qctx->client->message->rcode = dns_rcode_badcookie;
+ return (ns_query_done(qctx));
+ }
+
+ if (qctx->view->checknames &&
+ !dns_rdata_checkowner(qctx->client->query.qname,
+ qctx->client->message->rdclass, qctx->qtype,
+ false))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(qctx->client->query.qname, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(qctx->qtype, typebuf, sizeof(typebuf));
+ dns_rdataclass_format(qctx->client->message->rdclass, classbuf,
+ sizeof(classbuf));
+ ns_client_log(qctx->client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_QUERY, ISC_LOG_ERROR,
+ "check-names failure %s/%s/%s", namebuf, typebuf,
+ classbuf);
+ QUERY_ERROR(qctx, DNS_R_REFUSED);
+ return (ns_query_done(qctx));
+ }
+
+ /*
+ * Setup for root key sentinel processing.
+ */
+ if (qctx->view->root_key_sentinel &&
+ qctx->client->query.restarts == 0 &&
+ (qctx->qtype == dns_rdatatype_a ||
+ qctx->qtype == dns_rdatatype_aaaa) &&
+ (qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0)
+ {
+ root_key_sentinel_detect(qctx);
+ }
+
+ /*
+ * First we must find the right database.
+ */
+ qctx->options &= DNS_GETDB_NOLOG; /* Preserve DNS_GETDB_NOLOG. */
+ if (dns_rdatatype_atparent(qctx->qtype) &&
+ !dns_name_equal(qctx->client->query.qname, dns_rootname))
+ {
+ /*
+ * If authoritative data for this QTYPE is supposed to live in
+ * the parent zone, do not look for an exact match for QNAME,
+ * but rather for its containing zone (unless the QNAME is
+ * root).
+ */
+ qctx->options |= DNS_GETDB_NOEXACT;
+ }
+
+ result = query_getdb(qctx->client, qctx->client->query.qname,
+ qctx->qtype, qctx->options, &qctx->zone, &qctx->db,
+ &qctx->version, &qctx->is_zone);
+ if ((result != ISC_R_SUCCESS || !qctx->is_zone) &&
+ qctx->qtype == dns_rdatatype_ds && !RECURSIONOK(qctx->client) &&
+ (qctx->options & DNS_GETDB_NOEXACT) != 0)
+ {
+ /*
+ * This is a non-recursive QTYPE=DS query with QNAME whose
+ * parent we are not authoritative for. Check whether we are
+ * authoritative for QNAME, because if so, we need to send a
+ * "no data" response as required by RFC 4035, section 3.1.4.1.
+ */
+ dns_db_t *tdb = NULL;
+ dns_zone_t *tzone = NULL;
+ dns_dbversion_t *tversion = NULL;
+ isc_result_t tresult;
+
+ tresult = query_getzonedb(
+ qctx->client, qctx->client->query.qname, qctx->qtype,
+ DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion);
+ if (tresult == ISC_R_SUCCESS) {
+ /*
+ * We are authoritative for QNAME. Attach the relevant
+ * zone to query context, set result to ISC_R_SUCCESS.
+ */
+ qctx->options &= ~DNS_GETDB_NOEXACT;
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ if (qctx->db != NULL) {
+ dns_db_detach(&qctx->db);
+ }
+ if (qctx->zone != NULL) {
+ dns_zone_detach(&qctx->zone);
+ }
+ qctx->version = NULL;
+ RESTORE(qctx->version, tversion);
+ RESTORE(qctx->db, tdb);
+ RESTORE(qctx->zone, tzone);
+ qctx->is_zone = true;
+ result = ISC_R_SUCCESS;
+ } else {
+ /*
+ * We are not authoritative for QNAME. Clean up and
+ * leave result as it was.
+ */
+ if (tdb != NULL) {
+ dns_db_detach(&tdb);
+ }
+ if (tzone != NULL) {
+ dns_zone_detach(&tzone);
+ }
+ }
+ }
+ /*
+ * If we did not find a database from which we can answer the query,
+ * respond with either REFUSED or SERVFAIL, depending on what the
+ * result of query_getdb() was.
+ */
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_REFUSED) {
+ if (WANTRECURSION(qctx->client)) {
+ inc_stats(qctx->client,
+ ns_statscounter_recurserej);
+ } else {
+ inc_stats(qctx->client,
+ ns_statscounter_authrej);
+ }
+ if (!PARTIALANSWER(qctx->client)) {
+ QUERY_ERROR(qctx, DNS_R_REFUSED);
+ }
+ } else {
+ CCTRACE(ISC_LOG_ERROR, "ns__query_start: query_getdb "
+ "failed");
+ QUERY_ERROR(qctx, result);
+ }
+ return (ns_query_done(qctx));
+ }
+
+ /*
+ * We found a database from which we can answer the query. Update
+ * relevant query context flags if the answer is to be prepared using
+ * authoritative data.
+ */
+ qctx->is_staticstub_zone = false;
+ if (qctx->is_zone) {
+ qctx->authoritative = true;
+ if (qctx->zone != NULL) {
+ if (dns_zone_gettype(qctx->zone) == dns_zone_mirror) {
+ qctx->authoritative = false;
+ }
+ if (dns_zone_gettype(qctx->zone) == dns_zone_staticstub)
+ {
+ qctx->is_staticstub_zone = true;
+ }
+ }
+ }
+
+ /*
+ * Attach to the database which will be used to prepare the answer.
+ * Update query statistics.
+ */
+ if (qctx->event == NULL && qctx->client->query.restarts == 0) {
+ if (qctx->is_zone) {
+ if (qctx->zone != NULL) {
+ /*
+ * if is_zone = true, zone = NULL then this is
+ * a DLZ zone. Don't attempt to attach zone.
+ */
+ dns_zone_attach(qctx->zone,
+ &qctx->client->query.authzone);
+ }
+ dns_db_attach(qctx->db, &qctx->client->query.authdb);
+ }
+ qctx->client->query.authdbset = true;
+
+ /* Track TCP vs UDP stats per zone */
+ if (TCP(qctx->client)) {
+ inc_stats(qctx->client, ns_statscounter_tcp);
+ } else {
+ inc_stats(qctx->client, ns_statscounter_udp);
+ }
+ }
+
+ if (!qctx->is_zone && (qctx->view->staleanswerclienttimeout == 0) &&
+ dns_view_staleanswerenabled(qctx->view))
+ {
+ /*
+ * If stale answers are enabled and
+ * stale-answer-client-timeout is zero, then we can promptly
+ * answer with a stale RRset if one is available in cache.
+ */
+ qctx->options |= DNS_GETDB_STALEFIRST;
+ }
+
+ result = query_lookup(qctx);
+
+ /*
+ * Clear "look-also-for-stale-data" flag.
+ * If a fetch is created to resolve this query, then,
+ * when it completes, this option is not expected to be set.
+ */
+ qctx->options &= ~DNS_GETDB_STALEFIRST;
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Allocate buffers in 'qctx' used to store query results.
+ *
+ * 'buffer' must be a pointer to an object whose lifetime
+ * doesn't expire while 'qctx' is in use.
+ */
+static isc_result_t
+qctx_prepare_buffers(query_ctx_t *qctx, isc_buffer_t *buffer) {
+ REQUIRE(qctx != NULL);
+ REQUIRE(qctx->client != NULL);
+ REQUIRE(buffer != NULL);
+
+ qctx->dbuf = ns_client_getnamebuf(qctx->client);
+ if (qctx->dbuf == NULL) {
+ CCTRACE(ISC_LOG_ERROR,
+ "qctx_prepare_buffers: ns_client_getnamebuf "
+ "failed");
+ return (ISC_R_NOMEMORY);
+ }
+
+ qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, buffer);
+ if (qctx->fname == NULL) {
+ CCTRACE(ISC_LOG_ERROR,
+ "qctx_prepare_buffers: ns_client_newname failed");
+
+ return (ISC_R_NOMEMORY);
+ }
+
+ qctx->rdataset = ns_client_newrdataset(qctx->client);
+ if (qctx->rdataset == NULL) {
+ CCTRACE(ISC_LOG_ERROR,
+ "qctx_prepare_buffers: ns_client_newrdataset failed");
+ goto error;
+ }
+
+ if ((WANTDNSSEC(qctx->client) || qctx->findcoveringnsec) &&
+ (!qctx->is_zone || dns_db_issecure(qctx->db)))
+ {
+ qctx->sigrdataset = ns_client_newrdataset(qctx->client);
+ if (qctx->sigrdataset == NULL) {
+ CCTRACE(ISC_LOG_ERROR,
+ "qctx_prepare_buffers: "
+ "ns_client_newrdataset failed (2)");
+ goto error;
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+
+error:
+ if (qctx->fname != NULL) {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+ if (qctx->rdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ }
+
+ return (ISC_R_NOMEMORY);
+}
+
+/*
+ * Setup a new query context for resolving a query.
+ *
+ * This function is only called if both these conditions are met:
+ * 1. BIND is configured with stale-answer-client-timeout 0.
+ * 2. A stale RRset is found in cache during initial query
+ * database lookup.
+ *
+ * We continue with this function for refreshing/resolving an RRset
+ * after answering a client with stale data.
+ */
+static void
+query_refresh_rrset(query_ctx_t *orig_qctx) {
+ isc_buffer_t buffer;
+ query_ctx_t qctx;
+
+ REQUIRE(orig_qctx != NULL);
+ REQUIRE(orig_qctx->client != NULL);
+
+ qctx_copy(orig_qctx, &qctx);
+ qctx.client->query.dboptions &= ~(DNS_DBFIND_STALETIMEOUT |
+ DNS_DBFIND_STALEOK |
+ DNS_DBFIND_STALEENABLED);
+ qctx.client->nodetach = false;
+
+ /*
+ * We'll need some resources...
+ */
+ if (qctx_prepare_buffers(&qctx, &buffer) != ISC_R_SUCCESS) {
+ dns_db_detach(&qctx.db);
+ qctx_destroy(&qctx);
+ return;
+ }
+
+ /*
+ * Pretend we didn't find anything in cache.
+ */
+ (void)query_gotanswer(&qctx, ISC_R_NOTFOUND);
+
+ if (qctx.fname != NULL) {
+ ns_client_releasename(qctx.client, &qctx.fname);
+ }
+ if (qctx.rdataset != NULL) {
+ ns_client_putrdataset(qctx.client, &qctx.rdataset);
+ }
+
+ qctx_destroy(&qctx);
+}
+
+/*%
+ * Depending on the db lookup result, we can respond to the
+ * client this stale answer.
+ */
+static bool
+stale_client_answer(isc_result_t result) {
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXRRSET:
+ case DNS_R_NCACHENXRRSET:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ return (true);
+ default:
+ return (false);
+ }
+
+ UNREACHABLE();
+}
+
+/*%
+ * Perform a local database lookup, in either an authoritative or
+ * cache database. If unable to answer, call ns_query_done(); otherwise
+ * hand off processing to query_gotanswer().
+ */
+static isc_result_t
+query_lookup(query_ctx_t *qctx) {
+ isc_buffer_t buffer;
+ isc_result_t result = ISC_R_UNSET;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_name_t *rpzqname = NULL;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ unsigned int dboptions;
+ dns_ttl_t stale_refresh = 0;
+ bool dbfind_stale = false;
+ bool stale_timeout = false;
+ bool answer_found = false;
+ bool stale_found = false;
+ bool stale_refresh_window = false;
+ uint16_t ede = 0;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_lookup");
+
+ CALL_HOOK(NS_QUERY_LOOKUP_BEGIN, qctx);
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, qctx->client, NULL);
+ if (HAVEECS(qctx->client)) {
+ dns_clientinfo_setecs(&ci, &qctx->client->ecs);
+ }
+
+ /*
+ * We'll need some resources...
+ */
+ result = qctx_prepare_buffers(qctx, &buffer);
+ if (result != ISC_R_SUCCESS) {
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+
+ /*
+ * Now look for an answer in the database.
+ */
+ if (qctx->dns64 && qctx->rpz) {
+ rpzqname = qctx->client->query.rpz_st->p_name;
+ } else {
+ rpzqname = qctx->client->query.qname;
+ }
+
+ if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
+ /*
+ * If DNS_GETDB_STALEFIRST is set, it means that a stale
+ * RRset may be returned as part of this lookup. An attempt
+ * to refresh the RRset will still take place if an
+ * active RRset is not available.
+ */
+ qctx->client->query.dboptions |= DNS_DBFIND_STALETIMEOUT;
+ }
+
+ dboptions = qctx->client->query.dboptions;
+ if (!qctx->is_zone && qctx->findcoveringnsec &&
+ (qctx->type != dns_rdatatype_null || !dns_name_istat(rpzqname)))
+ {
+ dboptions |= DNS_DBFIND_COVERINGNSEC;
+ }
+
+ (void)dns_db_getservestalerefresh(qctx->client->view->cachedb,
+ &stale_refresh);
+ if (stale_refresh > 0 &&
+ dns_view_staleanswerenabled(qctx->client->view))
+ {
+ dboptions |= DNS_DBFIND_STALEENABLED;
+ }
+
+ result = dns_db_findext(qctx->db, rpzqname, qctx->version, qctx->type,
+ dboptions, qctx->client->now, &qctx->node,
+ qctx->fname, &cm, &ci, qctx->rdataset,
+ qctx->sigrdataset);
+
+ /*
+ * Fixup fname and sigrdataset.
+ */
+ if (qctx->dns64 && qctx->rpz) {
+ dns_name_copy(qctx->client->query.qname, qctx->fname);
+ if (qctx->sigrdataset != NULL &&
+ dns_rdataset_isassociated(qctx->sigrdataset))
+ {
+ dns_rdataset_disassociate(qctx->sigrdataset);
+ }
+ }
+
+ if (!qctx->is_zone) {
+ dns_cache_updatestats(qctx->view->cache, result);
+ }
+
+ /*
+ * If DNS_DBFIND_STALEOK is set this means we are dealing with a
+ * lookup following a failed lookup and it is okay to serve a stale
+ * answer. This will (re)start the 'stale-refresh-time' window in
+ * rbtdb, tracking the last time the RRset lookup failed.
+ */
+ dbfind_stale = ((dboptions & DNS_DBFIND_STALEOK) != 0);
+
+ /*
+ * If DNS_DBFIND_STALEENABLED is set, this may be a normal lookup, but
+ * we are allowed to immediately respond with a stale answer if the
+ * request is within the 'stale-refresh-time' window.
+ */
+ stale_refresh_window = (STALE_WINDOW(qctx->rdataset) &&
+ (dboptions & DNS_DBFIND_STALEENABLED) != 0);
+
+ /*
+ * If DNS_DBFIND_STALETIMEOUT is set, a stale answer is requested.
+ * This can happen if 'stale-answer-client-timeout' is enabled.
+ *
+ * If 'stale-answer-client-timeout' is set to 0, and a stale
+ * answer is found, send it to the client, and try to refresh the
+ * RRset.
+ *
+ * If 'stale-answer-client-timeout' is non-zero, and a stale
+ * answer is found, send it to the client. Don't try to refresh the
+ * RRset because a fetch is already in progress.
+ */
+ stale_timeout = ((dboptions & DNS_DBFIND_STALETIMEOUT) != 0);
+
+ if (dns_rdataset_isassociated(qctx->rdataset) &&
+ dns_rdataset_count(qctx->rdataset) > 0 && !STALE(qctx->rdataset))
+ {
+ /* Found non-stale usable rdataset. */
+ answer_found = true;
+ }
+
+ if (dbfind_stale || stale_refresh_window || stale_timeout) {
+ dns_name_format(qctx->client->query.qname, namebuf,
+ sizeof(namebuf));
+
+ inc_stats(qctx->client, ns_statscounter_trystale);
+
+ if (dns_rdataset_isassociated(qctx->rdataset) &&
+ dns_rdataset_count(qctx->rdataset) > 0 &&
+ STALE(qctx->rdataset))
+ {
+ stale_found = true;
+ if (result == DNS_R_NCACHENXDOMAIN ||
+ result == DNS_R_NXDOMAIN)
+ {
+ ede = DNS_EDE_STALENXANSWER;
+ } else {
+ ede = DNS_EDE_STALEANSWER;
+ }
+ qctx->rdataset->ttl = qctx->view->staleanswerttl;
+ inc_stats(qctx->client, ns_statscounter_usedstale);
+ } else {
+ stale_found = false;
+ }
+ }
+
+ if (dbfind_stale) {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s resolver failure, stale answer %s", namebuf,
+ stale_found ? "used" : "unavailable");
+ if (stale_found) {
+ ns_client_extendederror(qctx->client, ede,
+ "resolver failure");
+ } else if (!answer_found) {
+ /*
+ * Resolver failure, no stale data, nothing more we
+ * can do, return SERVFAIL.
+ */
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+ } else if (stale_refresh_window) {
+ /*
+ * A recent lookup failed, so during this time window we are
+ * allowed to return stale data immediately.
+ */
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s query within stale refresh time, stale "
+ "answer %s",
+ namebuf, stale_found ? "used" : "unavailable");
+
+ if (stale_found) {
+ ns_client_extendederror(
+ qctx->client, ede,
+ "query within stale refresh time window");
+ } else if (!answer_found) {
+ /*
+ * During the stale refresh window explicitly do not try
+ * to refresh the data, because a recent lookup failed.
+ */
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+ } else if (stale_timeout) {
+ if ((qctx->options & DNS_GETDB_STALEFIRST) != 0) {
+ if (!stale_found && !answer_found) {
+ /*
+ * We have nothing useful in cache to return
+ * immediately.
+ */
+ qctx_clean(qctx);
+ qctx_freedata(qctx);
+ dns_db_attach(qctx->client->view->cachedb,
+ &qctx->db);
+ qctx->client->query.dboptions &=
+ ~DNS_DBFIND_STALETIMEOUT;
+ qctx->options &= ~DNS_GETDB_STALEFIRST;
+ if (qctx->client->query.fetch != NULL) {
+ dns_resolver_destroyfetch(
+ &qctx->client->query.fetch);
+ }
+ return (query_lookup(qctx));
+ } else if (stale_client_answer(result)) {
+ /*
+ * Immediately return the stale answer, start a
+ * resolver fetch to refresh the data in cache.
+ */
+ isc_log_write(
+ ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s stale answer used, an attempt to "
+ "refresh the RRset will still be made",
+ namebuf);
+
+ qctx->refresh_rrset = STALE(qctx->rdataset);
+ /*
+ * If we are refreshing the RRSet, we must not
+ * detach from the client in query_send().
+ */
+ qctx->client->nodetach = qctx->refresh_rrset;
+
+ if (stale_found) {
+ ns_client_extendederror(
+ qctx->client, ede,
+ "stale data prioritized over "
+ "lookup");
+ }
+ }
+ } else {
+ /*
+ * The 'stale-answer-client-timeout' triggered, return
+ * the stale answer if available, otherwise wait until
+ * the resolver finishes.
+ */
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_SERVE_STALE,
+ NS_LOGMODULE_QUERY, ISC_LOG_INFO,
+ "%s client timeout, stale answer %s",
+ namebuf,
+ stale_found ? "used" : "unavailable");
+ if (stale_found) {
+ ns_client_extendederror(qctx->client, ede,
+ "client timeout");
+ } else if (!answer_found) {
+ return (result);
+ }
+
+ if (!stale_client_answer(result)) {
+ return (result);
+ }
+
+ /*
+ * There still might be real answer later. Mark the
+ * query so we'll know we can skip answering.
+ */
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_STALEPENDING;
+ }
+ }
+
+ if (stale_timeout && (answer_found || stale_found)) {
+ /*
+ * Mark RRsets that we are adding to the client message on a
+ * lookup during 'stale-answer-client-timeout', so we can
+ * clean it up if needed when we resume from recursion.
+ */
+ qctx->client->query.attributes |= NS_QUERYATTR_STALEOK;
+ qctx->rdataset->attributes |= DNS_RDATASETATTR_STALE_ADDED;
+ }
+
+ result = query_gotanswer(qctx, result);
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Clear all rdatasets from the message that are in the given section and
+ * that have the 'attr' attribute set.
+ */
+static void
+message_clearrdataset(dns_message_t *msg, unsigned int attr) {
+ 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 = DNS_SECTION_ANSWER; i < DNS_SECTION_MAX; i++) {
+ name = ISC_LIST_HEAD(msg->sections[i]);
+ while (name != NULL) {
+ next_name = ISC_LIST_NEXT(name, link);
+
+ rds = ISC_LIST_HEAD(name->list);
+ while (rds != NULL) {
+ next_rds = ISC_LIST_NEXT(rds, link);
+ if ((rds->attributes & attr) != attr) {
+ rds = next_rds;
+ continue;
+ }
+ ISC_LIST_UNLINK(name->list, rds, link);
+ INSIST(dns_rdataset_isassociated(rds));
+ dns_rdataset_disassociate(rds);
+ isc_mempool_put(msg->rdspool, rds);
+ rds = next_rds;
+ }
+
+ if (ISC_LIST_EMPTY(name->list)) {
+ ISC_LIST_UNLINK(msg->sections[i], name, link);
+ if (dns_name_dynamic(name)) {
+ dns_name_free(name, msg->mctx);
+ }
+ isc_mempool_put(msg->namepool, name);
+ }
+
+ name = next_name;
+ }
+ }
+}
+
+/*
+ * Clear any rdatasets from the client's message that were added on a lookup
+ * due to a client timeout.
+ */
+static void
+query_clear_stale(ns_client_t *client) {
+ message_clearrdataset(client->message, DNS_RDATASETATTR_STALE_ADDED);
+}
+
+/*
+ * Create a new query context with the sole intent of looking up for a stale
+ * RRset in cache. If an entry is found, we mark the original query as
+ * answered, in order to avoid answering the query twice, when the original
+ * fetch finishes.
+ */
+static void
+query_lookup_stale(ns_client_t *client) {
+ query_ctx_t qctx;
+
+ qctx_init(client, NULL, client->query.qtype, &qctx);
+ dns_db_attach(client->view->cachedb, &qctx.db);
+ client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK;
+ client->query.dboptions |= DNS_DBFIND_STALETIMEOUT;
+ client->nodetach = true;
+ (void)query_lookup(&qctx);
+ if (qctx.node != NULL) {
+ dns_db_detachnode(qctx.db, &qctx.node);
+ }
+ qctx_freedata(&qctx);
+ qctx_destroy(&qctx);
+}
+
+/*
+ * Event handler to resume processing a query after recursion, or when a
+ * client timeout is triggered. If the query has timed out or been cancelled
+ * or the system is shutting down, clean up and exit. If a client timeout is
+ * triggered, see if we can respond with a stale answer from cache. Otherwise,
+ * call query_resume() to continue the ongoing work.
+ */
+static void
+fetch_callback(isc_task_t *task, isc_event_t *event) {
+ dns_fetchevent_t *devent = (dns_fetchevent_t *)event;
+ dns_fetch_t *fetch = NULL;
+ ns_client_t *client = NULL;
+ bool fetch_canceled = false;
+ bool fetch_answered = false;
+ bool client_shuttingdown = false;
+ isc_logcategory_t *logcategory = NS_LOGCATEGORY_QUERY_ERRORS;
+ isc_result_t result;
+ int errorloglevel;
+ query_ctx_t qctx;
+
+ UNUSED(task);
+
+ REQUIRE(event->ev_type == DNS_EVENT_FETCHDONE ||
+ event->ev_type == DNS_EVENT_TRYSTALE);
+
+ client = devent->ev_arg;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(task == client->task);
+ REQUIRE(RECURSING(client));
+
+ CTRACE(ISC_LOG_DEBUG(3), "fetch_callback");
+
+ if (event->ev_type == DNS_EVENT_TRYSTALE) {
+ if (devent->result != ISC_R_CANCELED) {
+ query_lookup_stale(client);
+ }
+ isc_event_free(ISC_EVENT_PTR(&event));
+ return;
+ }
+ /*
+ * We are resuming from recursion. Reset any attributes, options
+ * that a lookup due to stale-answer-client-timeout may have set.
+ */
+ if (client->view->cachedb != NULL && client->view->recursion) {
+ client->query.attributes |= NS_QUERYATTR_RECURSIONOK;
+ }
+ client->query.fetchoptions &= ~DNS_FETCHOPT_TRYSTALE_ONTIMEOUT;
+ client->query.dboptions &= ~DNS_DBFIND_STALETIMEOUT;
+ client->nodetach = false;
+
+ LOCK(&client->query.fetchlock);
+ INSIST(client->query.fetch == devent->fetch ||
+ client->query.fetch == NULL);
+ if (QUERY_STALEPENDING(&client->query)) {
+ /*
+ * We've gotten an authoritative answer to a query that
+ * was left pending after a stale timeout. We don't need
+ * to do anything with it; free all the data and go home.
+ */
+ client->query.fetch = NULL;
+ fetch_answered = true;
+ } else if (client->query.fetch != NULL) {
+ /*
+ * This is the fetch we've been waiting for.
+ */
+ INSIST(devent->fetch == client->query.fetch);
+ client->query.fetch = NULL;
+
+ /*
+ * Update client->now.
+ */
+ isc_stdtime_get(&client->now);
+ } else {
+ /*
+ * This is a fetch completion event for a canceled fetch.
+ * Clean up and don't resume the find.
+ */
+ fetch_canceled = true;
+ }
+ UNLOCK(&client->query.fetchlock);
+
+ SAVE(fetch, devent->fetch);
+
+ /*
+ * We're done recursing, detach from quota and unlink from
+ * the manager's recursing-clients list.
+ */
+
+ if (client->recursionquota != NULL) {
+ isc_quota_detach(&client->recursionquota);
+ ns_stats_decrement(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ }
+
+ LOCK(&client->manager->reclock);
+ if (ISC_LINK_LINKED(client, rlink)) {
+ ISC_LIST_UNLINK(client->manager->recursing, client, rlink);
+ }
+ UNLOCK(&client->manager->reclock);
+
+ isc_nmhandle_detach(&client->fetchhandle);
+
+ client->query.attributes &= ~NS_QUERYATTR_RECURSING;
+ client->state = NS_CLIENTSTATE_WORKING;
+
+ /*
+ * Initialize a new qctx and use it to either resume from
+ * recursion or clean up after cancelation. Transfer
+ * ownership of devent to the new qctx in the process.
+ */
+ qctx_init(client, &devent, 0, &qctx);
+
+ client_shuttingdown = ns_client_shuttingdown(client);
+ if (fetch_canceled || fetch_answered || client_shuttingdown) {
+ /*
+ * We've timed out or are shutting down. We can now
+ * free the event and other resources held by qctx, but
+ * don't call qctx_destroy() yet: it might destroy the
+ * client, which we still need for a moment.
+ */
+ qctx_freedata(&qctx);
+
+ /*
+ * Return an error to the client, or just drop.
+ */
+ if (fetch_canceled) {
+ CTRACE(ISC_LOG_ERROR, "fetch cancelled");
+ query_error(client, DNS_R_SERVFAIL, __LINE__);
+ } else {
+ query_next(client, ISC_R_CANCELED);
+ }
+
+ /*
+ * Free any persistent plugin data that was allocated to
+ * service the client, then detach the client object.
+ */
+ qctx.detach_client = true;
+ qctx_destroy(&qctx);
+ } else {
+ /*
+ * Resume the find process.
+ */
+ query_trace(&qctx);
+
+ result = query_resume(&qctx);
+ if (result != ISC_R_SUCCESS) {
+ if (result == DNS_R_SERVFAIL) {
+ errorloglevel = ISC_LOG_DEBUG(2);
+ } else {
+ errorloglevel = ISC_LOG_DEBUG(4);
+ }
+ if (isc_log_wouldlog(ns_lctx, errorloglevel)) {
+ dns_resolver_logfetch(fetch, ns_lctx,
+ logcategory,
+ NS_LOGMODULE_QUERY,
+ errorloglevel, false);
+ }
+ }
+
+ qctx_destroy(&qctx);
+ }
+
+ dns_resolver_destroyfetch(&fetch);
+}
+
+/*%
+ * Check whether the recursion parameters in 'param' match the current query's
+ * recursion parameters provided in 'qtype', 'qname', and 'qdomain'.
+ */
+static bool
+recparam_match(const ns_query_recparam_t *param, dns_rdatatype_t qtype,
+ const dns_name_t *qname, const dns_name_t *qdomain) {
+ REQUIRE(param != NULL);
+
+ return (param->qtype == qtype && param->qname != NULL &&
+ qname != NULL && param->qdomain != NULL && qdomain != NULL &&
+ dns_name_equal(param->qname, qname) &&
+ dns_name_equal(param->qdomain, qdomain));
+}
+
+/*%
+ * Update 'param' with current query's recursion parameters provided in
+ * 'qtype', 'qname', and 'qdomain'.
+ */
+static void
+recparam_update(ns_query_recparam_t *param, dns_rdatatype_t qtype,
+ const dns_name_t *qname, const dns_name_t *qdomain) {
+ REQUIRE(param != NULL);
+
+ param->qtype = qtype;
+
+ if (qname == NULL) {
+ param->qname = NULL;
+ } else {
+ param->qname = dns_fixedname_initname(&param->fqname);
+ dns_name_copy(qname, param->qname);
+ }
+
+ if (qdomain == NULL) {
+ param->qdomain = NULL;
+ } else {
+ param->qdomain = dns_fixedname_initname(&param->fqdomain);
+ dns_name_copy(qdomain, param->qdomain);
+ }
+}
+static atomic_uint_fast32_t last_soft, last_hard;
+
+/*%
+ * Check recursion quota before making the current client "recursing".
+ */
+static isc_result_t
+check_recursionquota(ns_client_t *client) {
+ isc_result_t result = ISC_R_SUCCESS;
+
+ /*
+ * We are about to recurse, which means that this client will
+ * be unavailable for serving new requests for an indeterminate
+ * amount of time. If this client is currently responsible
+ * for handling incoming queries, set up a new client
+ * object to handle them while we are waiting for a
+ * response. There is no need to replace TCP clients
+ * because those have already been replaced when the
+ * connection was accepted (if allowed by the TCP quota).
+ */
+ if (client->recursionquota == NULL) {
+ result = isc_quota_attach(&client->sctx->recursionquota,
+ &client->recursionquota);
+ if (result == ISC_R_SUCCESS || result == ISC_R_SOFTQUOTA) {
+ ns_stats_increment(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ }
+
+ if (result == ISC_R_SOFTQUOTA) {
+ isc_stdtime_t now;
+ isc_stdtime_get(&now);
+ if (now != atomic_load_relaxed(&last_soft)) {
+ atomic_store_relaxed(&last_soft, now);
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY,
+ ISC_LOG_WARNING,
+ "recursive-clients soft limit "
+ "exceeded (%u/%u/%u), "
+ "aborting oldest query",
+ isc_quota_getused(
+ client->recursionquota),
+ isc_quota_getsoft(
+ client->recursionquota),
+ isc_quota_getmax(
+ client->recursionquota));
+ }
+ ns_client_killoldestquery(client);
+ result = ISC_R_SUCCESS;
+ } else if (result == ISC_R_QUOTA) {
+ isc_stdtime_t now;
+ isc_stdtime_get(&now);
+ if (now != atomic_load_relaxed(&last_hard)) {
+ ns_server_t *sctx = client->sctx;
+ atomic_store_relaxed(&last_hard, now);
+ ns_client_log(
+ client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+ "no more recursive clients "
+ "(%u/%u/%u): %s",
+ isc_quota_getused(
+ &sctx->recursionquota),
+ isc_quota_getsoft(
+ &sctx->recursionquota),
+ isc_quota_getmax(&sctx->recursionquota),
+ isc_result_totext(result));
+ }
+ ns_client_killoldestquery(client);
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_message_clonebuffer(client->message);
+ ns_client_recursing(client);
+ }
+
+ return (result);
+}
+
+isc_result_t
+ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
+ dns_name_t *qdomain, dns_rdataset_t *nameservers,
+ bool resuming) {
+ isc_result_t result;
+ dns_rdataset_t *rdataset, *sigrdataset;
+ isc_sockaddr_t *peeraddr = NULL;
+
+ CTRACE(ISC_LOG_DEBUG(3), "ns_query_recurse");
+
+ /*
+ * Check recursion parameters from the previous query to see if they
+ * match. If not, update recursion parameters and proceed.
+ */
+ if (recparam_match(&client->query.recparam, qtype, qname, qdomain)) {
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT, NS_LOGMODULE_QUERY,
+ ISC_LOG_INFO, "recursion loop detected");
+ return (ISC_R_ALREADYRUNNING);
+ }
+
+ recparam_update(&client->query.recparam, qtype, qname, qdomain);
+
+ if (!resuming) {
+ inc_stats(client, ns_statscounter_recursion);
+ }
+
+ result = check_recursionquota(client);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * Invoke the resolver.
+ */
+ REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns);
+ REQUIRE(client->query.fetch == NULL);
+
+ rdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL) {
+ return (ISC_R_NOMEMORY);
+ }
+
+ if (WANTDNSSEC(client)) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ ns_client_putrdataset(client, &rdataset);
+ return (ISC_R_NOMEMORY);
+ }
+ } else {
+ sigrdataset = NULL;
+ }
+
+ if (!client->query.timerset) {
+ ns_client_settimeout(client, 60);
+ }
+
+ if (!TCP(client)) {
+ peeraddr = &client->peeraddr;
+ }
+
+ if (client->view->staleanswerclienttimeout > 0 &&
+ client->view->staleanswerclienttimeout != (uint32_t)-1 &&
+ dns_view_staleanswerenabled(client->view))
+ {
+ client->query.fetchoptions |= DNS_FETCHOPT_TRYSTALE_ONTIMEOUT;
+ }
+
+ isc_nmhandle_attach(client->handle, &client->fetchhandle);
+ result = dns_resolver_createfetch(
+ client->view->resolver, qname, qtype, qdomain, nameservers,
+ NULL, peeraddr, client->message->id, client->query.fetchoptions,
+ 0, NULL, client->task, fetch_callback, client, rdataset,
+ sigrdataset, &client->query.fetch);
+ if (result != ISC_R_SUCCESS) {
+ isc_nmhandle_detach(&client->fetchhandle);
+ ns_client_putrdataset(client, &rdataset);
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ }
+
+ /*
+ * We're now waiting for a fetch event. A client which is
+ * shutting down will not be destroyed until all the events
+ * have been received.
+ */
+
+ return (result);
+}
+
+/*%
+ * Restores the query context after resuming from recursion, and
+ * continues the query processing if needed.
+ */
+static isc_result_t
+query_resume(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_name_t *tname;
+ isc_buffer_t b;
+#ifdef WANT_QUERYTRACE
+ char mbuf[4 * DNS_NAME_FORMATSIZE];
+ char qbuf[DNS_NAME_FORMATSIZE];
+ char tbuf[DNS_RDATATYPE_FORMATSIZE];
+#endif /* ifdef WANT_QUERYTRACE */
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_resume");
+
+ CALL_HOOK(NS_QUERY_RESUME_BEGIN, qctx);
+
+ qctx->want_restart = false;
+
+ qctx->rpz_st = qctx->client->query.rpz_st;
+ if (qctx->rpz_st != NULL &&
+ (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0)
+ {
+ CCTRACE(ISC_LOG_DEBUG(3), "resume from RPZ recursion");
+#ifdef WANT_QUERYTRACE
+ {
+ char pbuf[DNS_NAME_FORMATSIZE] = "<unset>";
+ char fbuf[DNS_NAME_FORMATSIZE] = "<unset>";
+ if (qctx->rpz_st->r_name != NULL) {
+ dns_name_format(qctx->rpz_st->r_name, qbuf,
+ sizeof(qbuf));
+ } else {
+ snprintf(qbuf, sizeof(qbuf), "<unset>");
+ }
+ if (qctx->rpz_st->p_name != NULL) {
+ dns_name_format(qctx->rpz_st->p_name, pbuf,
+ sizeof(pbuf));
+ }
+ if (qctx->rpz_st->fname != NULL) {
+ dns_name_format(qctx->rpz_st->fname, fbuf,
+ sizeof(fbuf));
+ }
+
+ snprintf(mbuf, sizeof(mbuf) - 1,
+ "rpz rname:%s, pname:%s, qctx->fname:%s", qbuf,
+ pbuf, fbuf);
+ CCTRACE(ISC_LOG_DEBUG(3), mbuf);
+ }
+#endif /* ifdef WANT_QUERYTRACE */
+
+ qctx->is_zone = qctx->rpz_st->q.is_zone;
+ qctx->authoritative = qctx->rpz_st->q.authoritative;
+ RESTORE(qctx->zone, qctx->rpz_st->q.zone);
+ RESTORE(qctx->node, qctx->rpz_st->q.node);
+ RESTORE(qctx->db, qctx->rpz_st->q.db);
+ RESTORE(qctx->rdataset, qctx->rpz_st->q.rdataset);
+ RESTORE(qctx->sigrdataset, qctx->rpz_st->q.sigrdataset);
+ qctx->qtype = qctx->rpz_st->q.qtype;
+
+ if (qctx->event->node != NULL) {
+ dns_db_detachnode(qctx->event->db, &qctx->event->node);
+ }
+ SAVE(qctx->rpz_st->r.db, qctx->event->db);
+ qctx->rpz_st->r.r_type = qctx->event->qtype;
+ SAVE(qctx->rpz_st->r.r_rdataset, qctx->event->rdataset);
+ ns_client_putrdataset(qctx->client, &qctx->event->sigrdataset);
+ } else if (REDIRECT(qctx->client)) {
+ /*
+ * Restore saved state.
+ */
+ CCTRACE(ISC_LOG_DEBUG(3), "resume from redirect recursion");
+#ifdef WANT_QUERYTRACE
+ dns_name_format(qctx->client->query.redirect.fname, qbuf,
+ sizeof(qbuf));
+ dns_rdatatype_format(qctx->client->query.redirect.qtype, tbuf,
+ sizeof(tbuf));
+ snprintf(mbuf, sizeof(mbuf) - 1,
+ "redirect qctx->fname:%s, qtype:%s, auth:%d", qbuf,
+ tbuf, qctx->client->query.redirect.authoritative);
+ CCTRACE(ISC_LOG_DEBUG(3), mbuf);
+#endif /* ifdef WANT_QUERYTRACE */
+ qctx->qtype = qctx->client->query.redirect.qtype;
+ INSIST(qctx->client->query.redirect.rdataset != NULL);
+ RESTORE(qctx->rdataset, qctx->client->query.redirect.rdataset);
+ RESTORE(qctx->sigrdataset,
+ qctx->client->query.redirect.sigrdataset);
+ RESTORE(qctx->db, qctx->client->query.redirect.db);
+ RESTORE(qctx->node, qctx->client->query.redirect.node);
+ RESTORE(qctx->zone, qctx->client->query.redirect.zone);
+ qctx->authoritative =
+ qctx->client->query.redirect.authoritative;
+
+ /*
+ * Free resources used while recursing.
+ */
+ ns_client_putrdataset(qctx->client, &qctx->event->rdataset);
+ ns_client_putrdataset(qctx->client, &qctx->event->sigrdataset);
+ if (qctx->event->node != NULL) {
+ dns_db_detachnode(qctx->event->db, &qctx->event->node);
+ }
+ if (qctx->event->db != NULL) {
+ dns_db_detach(&qctx->event->db);
+ }
+ } else {
+ CCTRACE(ISC_LOG_DEBUG(3), "resume from normal recursion");
+ qctx->authoritative = false;
+
+ qctx->qtype = qctx->event->qtype;
+ SAVE(qctx->db, qctx->event->db);
+ SAVE(qctx->node, qctx->event->node);
+ SAVE(qctx->rdataset, qctx->event->rdataset);
+ SAVE(qctx->sigrdataset, qctx->event->sigrdataset);
+ }
+ INSIST(qctx->rdataset != NULL);
+
+ if (qctx->qtype == dns_rdatatype_rrsig ||
+ qctx->qtype == dns_rdatatype_sig)
+ {
+ qctx->type = dns_rdatatype_any;
+ } else {
+ qctx->type = qctx->qtype;
+ }
+
+ CALL_HOOK(NS_QUERY_RESUME_RESTORED, qctx);
+
+ if (DNS64(qctx->client)) {
+ qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64;
+ qctx->dns64 = true;
+ }
+
+ if (DNS64EXCLUDE(qctx->client)) {
+ qctx->client->query.attributes &= ~NS_QUERYATTR_DNS64EXCLUDE;
+ qctx->dns64_exclude = true;
+ }
+
+ if (qctx->rpz_st != NULL &&
+ (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0)
+ {
+ /*
+ * Has response policy changed out from under us?
+ */
+ if (qctx->rpz_st->rpz_ver != qctx->view->rpzs->rpz_ver) {
+ ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY, DNS_RPZ_INFO_LEVEL,
+ "query_resume: RPZ settings "
+ "out of date "
+ "(rpz_ver %d, expected %d)",
+ qctx->view->rpzs->rpz_ver,
+ qctx->rpz_st->rpz_ver);
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+ }
+
+ /*
+ * We'll need some resources...
+ */
+ qctx->dbuf = ns_client_getnamebuf(qctx->client);
+ if (qctx->dbuf == NULL) {
+ CCTRACE(ISC_LOG_ERROR, "query_resume: ns_client_getnamebuf "
+ "failed (1)");
+ QUERY_ERROR(qctx, ISC_R_NOMEMORY);
+ return (ns_query_done(qctx));
+ }
+
+ qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b);
+ if (qctx->fname == NULL) {
+ CCTRACE(ISC_LOG_ERROR, "query_resume: ns_client_newname failed "
+ "(1)");
+ QUERY_ERROR(qctx, ISC_R_NOMEMORY);
+ return (ns_query_done(qctx));
+ }
+
+ if (qctx->rpz_st != NULL &&
+ (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0)
+ {
+ tname = qctx->rpz_st->fname;
+ } else if (REDIRECT(qctx->client)) {
+ tname = qctx->client->query.redirect.fname;
+ } else {
+ tname = qctx->event->foundname;
+ }
+
+ dns_name_copy(tname, qctx->fname);
+
+ if (qctx->rpz_st != NULL &&
+ (qctx->rpz_st->state & DNS_RPZ_RECURSING) != 0)
+ {
+ qctx->rpz_st->r.r_result = qctx->event->result;
+ result = qctx->rpz_st->q.result;
+ free_devent(qctx->client, ISC_EVENT_PTR(&qctx->event),
+ &qctx->event);
+ } else if (REDIRECT(qctx->client)) {
+ result = qctx->client->query.redirect.result;
+ } else {
+ result = qctx->event->result;
+ }
+
+ qctx->resuming = true;
+
+ return (query_gotanswer(qctx, result));
+
+cleanup:
+ return (result);
+}
+
+static void
+query_hookresume(isc_task_t *task, isc_event_t *event) {
+ ns_hook_resevent_t *rev = (ns_hook_resevent_t *)event;
+ ns_hookasync_t *hctx = NULL;
+ ns_client_t *client = rev->ev_arg;
+ query_ctx_t *qctx = rev->saved_qctx;
+ bool canceled;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_hookresume");
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(task == client->task);
+ REQUIRE(event->ev_type == NS_EVENT_HOOKASYNCDONE);
+
+ LOCK(&client->query.fetchlock);
+ if (client->query.hookactx != NULL) {
+ INSIST(rev->ctx == client->query.hookactx);
+ client->query.hookactx = NULL;
+ canceled = false;
+ isc_stdtime_get(&client->now);
+ } else {
+ canceled = true;
+ }
+ UNLOCK(&client->query.fetchlock);
+ SAVE(hctx, rev->ctx);
+
+ if (client->recursionquota != NULL) {
+ isc_quota_detach(&client->recursionquota);
+ ns_stats_decrement(client->sctx->nsstats,
+ ns_statscounter_recursclients);
+ }
+
+ LOCK(&client->manager->reclock);
+ if (ISC_LINK_LINKED(client, rlink)) {
+ ISC_LIST_UNLINK(client->manager->recursing, client, rlink);
+ }
+ UNLOCK(&client->manager->reclock);
+
+ /*
+ * This event is running under a client task, so it's safe to detach
+ * the fetch handle. And it should be done before resuming query
+ * processing below, since that may trigger another recursion or
+ * asynchronous hook event.
+ */
+ isc_nmhandle_detach(&client->fetchhandle);
+
+ client->state = NS_CLIENTSTATE_WORKING;
+
+ if (canceled) {
+ /*
+ * Note: unlike fetch_callback, this function doesn't bother
+ * to check the 'shutdown' condition, as that doesn't seem to
+ * happen in the latest implementation.
+ */
+ query_error(client, DNS_R_SERVFAIL, __LINE__);
+
+ /*
+ * There's no other place to free/release any data maintained
+ * in qctx. We need to do it here to prevent leak.
+ */
+ qctx_clean(qctx);
+ qctx_freedata(qctx);
+
+ /*
+ * As we're almost done with this client, make sure any internal
+ * resource for hooks will be released (if necessary) via the
+ * QCTX_DESTROYED hook.
+ */
+ qctx->detach_client = true;
+ } else {
+ switch (rev->hookpoint) {
+ case NS_QUERY_SETUP:
+ (void)query_setup(client, qctx->qtype);
+ break;
+ case NS_QUERY_START_BEGIN:
+ (void)ns__query_start(qctx);
+ break;
+ case NS_QUERY_LOOKUP_BEGIN:
+ (void)query_lookup(qctx);
+ break;
+ case NS_QUERY_RESUME_BEGIN:
+ case NS_QUERY_RESUME_RESTORED:
+ (void)query_resume(qctx);
+ break;
+ case NS_QUERY_GOT_ANSWER_BEGIN:
+ (void)query_gotanswer(qctx, rev->origresult);
+ break;
+ case NS_QUERY_RESPOND_ANY_BEGIN:
+ (void)query_respond_any(qctx);
+ break;
+ case NS_QUERY_ADDANSWER_BEGIN:
+ (void)query_addanswer(qctx);
+ break;
+ case NS_QUERY_NOTFOUND_BEGIN:
+ (void)query_notfound(qctx);
+ break;
+ case NS_QUERY_PREP_DELEGATION_BEGIN:
+ (void)query_prepare_delegation_response(qctx);
+ break;
+ case NS_QUERY_ZONE_DELEGATION_BEGIN:
+ (void)query_zone_delegation(qctx);
+ break;
+ case NS_QUERY_DELEGATION_BEGIN:
+ (void)query_delegation(qctx);
+ break;
+ case NS_QUERY_DELEGATION_RECURSE_BEGIN:
+ (void)query_delegation_recurse(qctx);
+ break;
+ case NS_QUERY_NODATA_BEGIN:
+ (void)query_nodata(qctx, rev->origresult);
+ break;
+ case NS_QUERY_NXDOMAIN_BEGIN:
+ (void)query_nxdomain(qctx, rev->origresult);
+ break;
+ case NS_QUERY_NCACHE_BEGIN:
+ (void)query_ncache(qctx, rev->origresult);
+ break;
+ case NS_QUERY_CNAME_BEGIN:
+ (void)query_cname(qctx);
+ break;
+ case NS_QUERY_DNAME_BEGIN:
+ (void)query_dname(qctx);
+ break;
+ case NS_QUERY_RESPOND_BEGIN:
+ (void)query_respond(qctx);
+ break;
+ case NS_QUERY_PREP_RESPONSE_BEGIN:
+ (void)query_prepresponse(qctx);
+ break;
+ case NS_QUERY_DONE_BEGIN:
+ case NS_QUERY_DONE_SEND:
+ (void)ns_query_done(qctx);
+ break;
+
+ /* Not all hookpoints can use recursion. Catch violations */
+ case NS_QUERY_RESPOND_ANY_FOUND: /* due to side effect */
+ case NS_QUERY_NOTFOUND_RECURSE: /* in recursion */
+ case NS_QUERY_ZEROTTL_RECURSE: /* in recursion */
+ default: /* catch-all just in case */
+ INSIST(false);
+ }
+ }
+
+ hctx->destroy(&hctx);
+ qctx_destroy(qctx);
+ isc_mem_put(client->mctx, qctx, sizeof(*qctx));
+ isc_event_free(&event);
+}
+
+isc_result_t
+ns_query_hookasync(query_ctx_t *qctx, ns_query_starthookasync_t runasync,
+ void *arg) {
+ isc_result_t result;
+ ns_client_t *client = qctx->client;
+ query_ctx_t *saved_qctx = NULL;
+
+ CTRACE(ISC_LOG_DEBUG(3), "ns_query_hookasync");
+
+ REQUIRE(NS_CLIENT_VALID(client));
+ REQUIRE(client->query.hookactx == NULL);
+ REQUIRE(client->query.fetch == NULL);
+
+ result = check_recursionquota(client);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ saved_qctx = isc_mem_get(client->mctx, sizeof(*saved_qctx));
+ qctx_save(qctx, saved_qctx);
+ result = runasync(saved_qctx, client->mctx, arg, client->task,
+ query_hookresume, client, &client->query.hookactx);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Typically the runasync() function will trigger recursion, but
+ * there is no need to set NS_QUERYATTR_RECURSING. The calling hook
+ * is expected to return NS_HOOK_RETURN, and the RECURSING
+ * attribute won't be checked anywhere.
+ *
+ * Hook-based asynchronous processing cannot coincide with normal
+ * recursion, so we can safely use fetchhandle here. Unlike in
+ * ns_query_recurse(), we attach to the handle only if 'runasync'
+ * succeeds. It should be safe since we're either in the client
+ * task or pausing it.
+ */
+ isc_nmhandle_attach(client->handle, &client->fetchhandle);
+ return (ISC_R_SUCCESS);
+
+cleanup:
+ /*
+ * If we fail, send SERVFAIL now. It may be better to let the caller
+ * decide what to do on failure of this function, but hooks don't have
+ * access to query_error().
+ */
+ query_error(client, DNS_R_SERVFAIL, __LINE__);
+
+ /*
+ * Free all resource related to the query and set detach_client,
+ * similar to the cancel case of query_hookresume; the callers will
+ * simply return on failure of this function, so there's no other
+ * place for this to prevent leak.
+ */
+ if (saved_qctx != NULL) {
+ qctx_clean(saved_qctx);
+ qctx_freedata(saved_qctx);
+ qctx_destroy(saved_qctx);
+ isc_mem_put(client->mctx, saved_qctx, sizeof(*saved_qctx));
+ }
+ qctx->detach_client = true;
+ return (result);
+}
+
+/*%
+ * If the query is recursive, check the SERVFAIL cache to see whether
+ * identical queries have failed recently. If we find a match, and it was
+ * from a query with CD=1, *or* if the current query has CD=0, then we just
+ * return SERVFAIL again. This prevents a validation failure from eliciting a
+ * SERVFAIL response to a CD=1 query.
+ */
+isc_result_t
+ns__query_sfcache(query_ctx_t *qctx) {
+ bool failcache;
+ uint32_t flags;
+
+ /*
+ * The SERVFAIL cache doesn't apply to authoritative queries.
+ */
+ if (!RECURSIONOK(qctx->client)) {
+ return (ISC_R_COMPLETE);
+ }
+
+ flags = 0;
+#ifdef ENABLE_AFL
+ if (qctx->client->sctx->fuzztype == isc_fuzz_resolver) {
+ failcache = false;
+ } else {
+ failcache = dns_badcache_find(
+ qctx->view->failcache, qctx->client->query.qname,
+ qctx->qtype, &flags, &qctx->client->tnow);
+ }
+#else /* ifdef ENABLE_AFL */
+ failcache = dns_badcache_find(qctx->view->failcache,
+ qctx->client->query.qname, qctx->qtype,
+ &flags, &qctx->client->tnow);
+#endif /* ifdef ENABLE_AFL */
+ if (failcache &&
+ (((flags & NS_FAILCACHE_CD) != 0) ||
+ ((qctx->client->message->flags & DNS_MESSAGEFLAG_CD) == 0)))
+ {
+ if (isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_name_format(qctx->client->query.qname, namebuf,
+ sizeof(namebuf));
+ dns_rdatatype_format(qctx->qtype, typebuf,
+ sizeof(typebuf));
+ ns_client_log(qctx->client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_QUERY, ISC_LOG_DEBUG(1),
+ "servfail cache hit %s/%s (%s)", namebuf,
+ typebuf,
+ ((flags & NS_FAILCACHE_CD) != 0) ? "CD=1"
+ : "CD="
+ "0");
+ }
+
+ qctx->client->attributes |= NS_CLIENTATTR_NOSETFC;
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*%
+ * Handle response rate limiting (RRL).
+ */
+static isc_result_t
+query_checkrrl(query_ctx_t *qctx, isc_result_t result) {
+ /*
+ * Rate limit these responses to this client.
+ * Do not delay counting and handling obvious referrals,
+ * since those won't come here again.
+ * Delay handling delegations for which we are certain to recurse and
+ * return here (DNS_R_DELEGATION, not a child of one of our
+ * own zones, and recursion enabled)
+ * Don't mess with responses rewritten by RPZ
+ * Count each response at most once.
+ */
+
+ /*
+ * XXXMPA the rrl system tests fails sometimes and RRL_CHECKED
+ * is set when we are called the second time preventing the
+ * response being dropped.
+ */
+ ns_client_log(
+ qctx->client, DNS_LOGCATEGORY_RRL, NS_LOGMODULE_QUERY,
+ ISC_LOG_DEBUG(99),
+ "rrl=%p, HAVECOOKIE=%u, result=%s, "
+ "fname=%p(%u), is_zone=%u, RECURSIONOK=%u, "
+ "query.rpz_st=%p(%u), RRL_CHECKED=%u\n",
+ qctx->client->view->rrl, HAVECOOKIE(qctx->client),
+ isc_result_toid(result), qctx->fname,
+ qctx->fname != NULL ? dns_name_isabsolute(qctx->fname) : 0,
+ qctx->is_zone, RECURSIONOK(qctx->client),
+ qctx->client->query.rpz_st,
+ qctx->client->query.rpz_st != NULL
+ ? ((qctx->client->query.rpz_st->state &
+ DNS_RPZ_REWRITTEN) != 0)
+ : 0,
+ (qctx->client->query.attributes & NS_QUERYATTR_RRL_CHECKED) !=
+ 0);
+
+ if (qctx->view->rrl != NULL && !HAVECOOKIE(qctx->client) &&
+ ((qctx->fname != NULL && dns_name_isabsolute(qctx->fname)) ||
+ (result == ISC_R_NOTFOUND && !RECURSIONOK(qctx->client))) &&
+ !(result == DNS_R_DELEGATION && !qctx->is_zone &&
+ RECURSIONOK(qctx->client)) &&
+ (qctx->client->query.rpz_st == NULL ||
+ (qctx->client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) &&
+ (qctx->client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0)
+ {
+ dns_rdataset_t nc_rdataset;
+ bool wouldlog;
+ dns_fixedname_t fixed;
+ const dns_name_t *constname;
+ char log_buf[DNS_RRL_LOG_BUF_LEN];
+ isc_result_t nc_result, resp_result;
+ dns_rrl_result_t rrl_result;
+
+ qctx->client->query.attributes |= NS_QUERYATTR_RRL_CHECKED;
+
+ wouldlog = isc_log_wouldlog(ns_lctx, DNS_RRL_LOG_DROP);
+ constname = qctx->fname;
+ if (result == DNS_R_NXDOMAIN) {
+ /*
+ * Use the database origin name to rate limit NXDOMAIN
+ */
+ if (qctx->db != NULL) {
+ constname = dns_db_origin(qctx->db);
+ }
+ resp_result = result;
+ } else if (result == DNS_R_NCACHENXDOMAIN &&
+ qctx->rdataset != NULL &&
+ dns_rdataset_isassociated(qctx->rdataset) &&
+ (qctx->rdataset->attributes &
+ DNS_RDATASETATTR_NEGATIVE) != 0)
+ {
+ /*
+ * Try to use owner name in the negative cache SOA.
+ */
+ dns_fixedname_init(&fixed);
+ dns_rdataset_init(&nc_rdataset);
+ for (nc_result = dns_rdataset_first(qctx->rdataset);
+ nc_result == ISC_R_SUCCESS;
+ nc_result = dns_rdataset_next(qctx->rdataset))
+ {
+ dns_ncache_current(qctx->rdataset,
+ dns_fixedname_name(&fixed),
+ &nc_rdataset);
+ if (nc_rdataset.type == dns_rdatatype_soa) {
+ dns_rdataset_disassociate(&nc_rdataset);
+ constname = dns_fixedname_name(&fixed);
+ break;
+ }
+ dns_rdataset_disassociate(&nc_rdataset);
+ }
+ resp_result = DNS_R_NXDOMAIN;
+ } else if (result == DNS_R_NXRRSET || result == DNS_R_EMPTYNAME)
+ {
+ resp_result = DNS_R_NXRRSET;
+ } else if (result == DNS_R_DELEGATION) {
+ resp_result = result;
+ } else if (result == ISC_R_NOTFOUND) {
+ /*
+ * Handle referral to ".", including when recursion
+ * is off or not requested and the hints have not
+ * been loaded.
+ */
+ constname = dns_rootname;
+ resp_result = DNS_R_DELEGATION;
+ } else {
+ resp_result = ISC_R_SUCCESS;
+ }
+
+ rrl_result = dns_rrl(
+ qctx->view, qctx->zone, &qctx->client->peeraddr,
+ TCP(qctx->client), qctx->client->message->rdclass,
+ qctx->qtype, constname, resp_result, qctx->client->now,
+ wouldlog, log_buf, sizeof(log_buf));
+ if (rrl_result != DNS_RRL_RESULT_OK) {
+ /*
+ * Log dropped or slipped responses in the query
+ * category so that requests are not silently lost.
+ * Starts of rate-limited bursts are logged in
+ * DNS_LOGCATEGORY_RRL.
+ *
+ * Dropped responses are counted with dropped queries
+ * in QryDropped while slipped responses are counted
+ * with other truncated responses in RespTruncated.
+ */
+ if (wouldlog) {
+ ns_client_log(qctx->client, DNS_LOGCATEGORY_RRL,
+ NS_LOGMODULE_QUERY,
+ DNS_RRL_LOG_DROP, "%s", log_buf);
+ }
+
+ if (!qctx->view->rrl->log_only) {
+ if (rrl_result == DNS_RRL_RESULT_DROP) {
+ /*
+ * These will also be counted in
+ * ns_statscounter_dropped
+ */
+ inc_stats(qctx->client,
+ ns_statscounter_ratedropped);
+ QUERY_ERROR(qctx, DNS_R_DROP);
+ } else {
+ /*
+ * These will also be counted in
+ * ns_statscounter_truncatedresp
+ */
+ inc_stats(qctx->client,
+ ns_statscounter_rateslipped);
+ if (WANTCOOKIE(qctx->client)) {
+ qctx->client->message->flags &=
+ ~DNS_MESSAGEFLAG_AA;
+ qctx->client->message->flags &=
+ ~DNS_MESSAGEFLAG_AD;
+ qctx->client->message->rcode =
+ dns_rcode_badcookie;
+ } else {
+ qctx->client->message->flags |=
+ DNS_MESSAGEFLAG_TC;
+ if (resp_result ==
+ DNS_R_NXDOMAIN)
+ {
+ qctx->client->message
+ ->rcode =
+ dns_rcode_nxdomain;
+ }
+ }
+ }
+ return (DNS_R_DROP);
+ }
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Do any RPZ rewriting that may be needed for this query.
+ */
+static isc_result_t
+query_checkrpz(query_ctx_t *qctx, isc_result_t result) {
+ isc_result_t rresult;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_checkrpz");
+
+ rresult = rpz_rewrite(qctx->client, qctx->qtype, result, qctx->resuming,
+ qctx->rdataset, qctx->sigrdataset);
+ qctx->rpz_st = qctx->client->query.rpz_st;
+ switch (rresult) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_NOTFOUND:
+ case DNS_R_DISALLOWED:
+ return (result);
+ case DNS_R_DELEGATION:
+ /*
+ * recursing for NS names or addresses,
+ * so save the main query state
+ */
+ INSIST(!RECURSING(qctx->client));
+ qctx->rpz_st->q.qtype = qctx->qtype;
+ qctx->rpz_st->q.is_zone = qctx->is_zone;
+ qctx->rpz_st->q.authoritative = qctx->authoritative;
+ SAVE(qctx->rpz_st->q.zone, qctx->zone);
+ SAVE(qctx->rpz_st->q.db, qctx->db);
+ SAVE(qctx->rpz_st->q.node, qctx->node);
+ SAVE(qctx->rpz_st->q.rdataset, qctx->rdataset);
+ SAVE(qctx->rpz_st->q.sigrdataset, qctx->sigrdataset);
+ dns_name_copy(qctx->fname, qctx->rpz_st->fname);
+ qctx->rpz_st->q.result = result;
+ qctx->client->query.attributes |= NS_QUERYATTR_RECURSING;
+ return (ISC_R_COMPLETE);
+ default:
+ QUERY_ERROR(qctx, rresult);
+ return (ISC_R_COMPLETE);
+ }
+
+ if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS) {
+ qctx->rpz_st->state |= DNS_RPZ_REWRITTEN;
+ }
+
+ if (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_MISS &&
+ qctx->rpz_st->m.policy != DNS_RPZ_POLICY_PASSTHRU &&
+ (qctx->rpz_st->m.policy != DNS_RPZ_POLICY_TCP_ONLY ||
+ !TCP(qctx->client)) &&
+ qctx->rpz_st->m.policy != DNS_RPZ_POLICY_ERROR)
+ {
+ /*
+ * We got a hit and are going to answer with our
+ * fiction. Ensure that we answer with the name
+ * we looked up even if we were stopped short
+ * in recursion or for a deferral.
+ */
+ dns_name_copy(qctx->client->query.qname, qctx->fname);
+ rpz_clean(&qctx->zone, &qctx->db, &qctx->node, NULL);
+ if (qctx->rpz_st->m.rdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ RESTORE(qctx->rdataset, qctx->rpz_st->m.rdataset);
+ } else {
+ qctx_clean(qctx);
+ }
+ qctx->version = NULL;
+
+ RESTORE(qctx->node, qctx->rpz_st->m.node);
+ RESTORE(qctx->db, qctx->rpz_st->m.db);
+ RESTORE(qctx->version, qctx->rpz_st->m.version);
+ RESTORE(qctx->zone, qctx->rpz_st->m.zone);
+
+ /*
+ * Add SOA record to additional section
+ */
+ if (qctx->rpz_st->m.rpz->addsoa) {
+ bool override_ttl =
+ dns_rdataset_isassociated(qctx->rdataset);
+ rresult = query_addsoa(qctx, override_ttl,
+ DNS_SECTION_ADDITIONAL);
+ if (rresult != ISC_R_SUCCESS) {
+ QUERY_ERROR(qctx, result);
+ return (ISC_R_COMPLETE);
+ }
+ }
+
+ switch (qctx->rpz_st->m.policy) {
+ case DNS_RPZ_POLICY_TCP_ONLY:
+ qctx->client->message->flags |= DNS_MESSAGEFLAG_TC;
+ if (result == DNS_R_NXDOMAIN ||
+ result == DNS_R_NCACHENXDOMAIN)
+ {
+ qctx->client->message->rcode =
+ dns_rcode_nxdomain;
+ }
+ rpz_log_rewrite(qctx->client, false,
+ qctx->rpz_st->m.policy,
+ qctx->rpz_st->m.type, qctx->zone,
+ qctx->rpz_st->p_name, NULL,
+ qctx->rpz_st->m.rpz->num);
+ return (ISC_R_COMPLETE);
+ case DNS_RPZ_POLICY_DROP:
+ QUERY_ERROR(qctx, DNS_R_DROP);
+ rpz_log_rewrite(qctx->client, false,
+ qctx->rpz_st->m.policy,
+ qctx->rpz_st->m.type, qctx->zone,
+ qctx->rpz_st->p_name, NULL,
+ qctx->rpz_st->m.rpz->num);
+ return (ISC_R_COMPLETE);
+ case DNS_RPZ_POLICY_NXDOMAIN:
+ result = DNS_R_NXDOMAIN;
+ qctx->nxrewrite = true;
+ qctx->rpz = true;
+ break;
+ case DNS_RPZ_POLICY_NODATA:
+ qctx->nxrewrite = true;
+ FALLTHROUGH;
+ case DNS_RPZ_POLICY_DNS64:
+ result = DNS_R_NXRRSET;
+ qctx->rpz = true;
+ break;
+ case DNS_RPZ_POLICY_RECORD:
+ result = qctx->rpz_st->m.result;
+ if (qctx->qtype == dns_rdatatype_any &&
+ result != DNS_R_CNAME)
+ {
+ /*
+ * We will add all of the rdatasets of
+ * the node by iterating later,
+ * and set the TTL then.
+ */
+ if (dns_rdataset_isassociated(qctx->rdataset)) {
+ dns_rdataset_disassociate(
+ qctx->rdataset);
+ }
+ } else {
+ /*
+ * We will add this rdataset.
+ */
+ qctx->rdataset->ttl =
+ ISC_MIN(qctx->rdataset->ttl,
+ qctx->rpz_st->m.ttl);
+ }
+ qctx->rpz = true;
+ break;
+ case DNS_RPZ_POLICY_WILDCNAME: {
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+ result = dns_rdataset_first(qctx->rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(qctx->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+ result = query_rpzcname(qctx, &cname.cname);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_COMPLETE);
+ }
+ qctx->fname = NULL;
+ qctx->want_restart = true;
+ return (ISC_R_COMPLETE);
+ }
+ case DNS_RPZ_POLICY_CNAME:
+ /*
+ * Add overriding CNAME from a named.conf
+ * response-policy statement
+ */
+ result = query_rpzcname(qctx,
+ &qctx->rpz_st->m.rpz->cname);
+ if (result != ISC_R_SUCCESS) {
+ return (ISC_R_COMPLETE);
+ }
+ qctx->fname = NULL;
+ qctx->want_restart = true;
+ return (ISC_R_COMPLETE);
+ default:
+ UNREACHABLE();
+ }
+
+ /*
+ * Turn off DNSSEC because the results of a
+ * response policy zone cannot verify.
+ */
+ qctx->client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC |
+ NS_CLIENTATTR_WANTAD);
+ qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AD;
+ ns_client_putrdataset(qctx->client, &qctx->sigrdataset);
+ qctx->rpz_st->q.is_zone = qctx->is_zone;
+ qctx->is_zone = true;
+ rpz_log_rewrite(qctx->client, false, qctx->rpz_st->m.policy,
+ qctx->rpz_st->m.type, qctx->zone,
+ qctx->rpz_st->p_name, NULL,
+ qctx->rpz_st->m.rpz->num);
+ }
+
+ return (result);
+}
+
+/*%
+ * Add a CNAME to a query response, including translating foo.evil.com and
+ * *.evil.com CNAME *.example.com
+ * to
+ * foo.evil.com CNAME foo.evil.com.example.com
+ */
+static isc_result_t
+query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) {
+ ns_client_t *client;
+ dns_fixedname_t prefix, suffix;
+ unsigned int labels;
+ isc_result_t result;
+
+ REQUIRE(qctx != NULL && qctx->client != NULL);
+
+ client = qctx->client;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_rpzcname");
+
+ labels = dns_name_countlabels(cname);
+ if (labels > 2 && dns_name_iswildcard(cname)) {
+ dns_fixedname_init(&prefix);
+ dns_name_split(client->query.qname, 1,
+ dns_fixedname_name(&prefix), NULL);
+ dns_fixedname_init(&suffix);
+ dns_name_split(cname, labels - 1, NULL,
+ dns_fixedname_name(&suffix));
+ result = dns_name_concatenate(dns_fixedname_name(&prefix),
+ dns_fixedname_name(&suffix),
+ qctx->fname, NULL);
+ if (result == DNS_R_NAMETOOLONG) {
+ client->message->rcode = dns_rcode_yxdomain;
+ } else if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ } else {
+ dns_name_copy(cname, qctx->fname);
+ }
+
+ ns_client_keepname(client, qctx->fname, qctx->dbuf);
+ result = query_addcname(qctx, dns_trust_authanswer,
+ qctx->rpz_st->m.ttl);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ rpz_log_rewrite(client, false, qctx->rpz_st->m.policy,
+ qctx->rpz_st->m.type, qctx->rpz_st->m.zone,
+ qctx->rpz_st->p_name, qctx->fname,
+ qctx->rpz_st->m.rpz->num);
+
+ ns_client_qnamereplace(client, qctx->fname);
+
+ /*
+ * Turn off DNSSEC because the results of a
+ * response policy zone cannot verify.
+ */
+ client->attributes &= ~(NS_CLIENTATTR_WANTDNSSEC |
+ NS_CLIENTATTR_WANTAD);
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Check the configured trust anchors for a root zone trust anchor
+ * with a key id that matches qctx->client->query.root_key_sentinel_keyid.
+ *
+ * Return true when found, otherwise return false.
+ */
+static bool
+has_ta(query_ctx_t *qctx) {
+ dns_keytable_t *keytable = NULL;
+ dns_keynode_t *keynode = NULL;
+ dns_rdataset_t dsset;
+ dns_keytag_t sentinel = qctx->client->query.root_key_sentinel_keyid;
+ isc_result_t result;
+
+ result = dns_view_getsecroots(qctx->view, &keytable);
+ if (result != ISC_R_SUCCESS) {
+ return (false);
+ }
+
+ result = dns_keytable_find(keytable, dns_rootname, &keynode);
+ if (result != ISC_R_SUCCESS) {
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(keytable, &keynode);
+ }
+ dns_keytable_detach(&keytable);
+ return (false);
+ }
+
+ 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 rdata = DNS_RDATA_INIT;
+ dns_rdata_ds_t ds;
+
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&dsset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &ds, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (ds.key_tag == sentinel) {
+ dns_keytable_detachkeynode(keytable, &keynode);
+ dns_keytable_detach(&keytable);
+ dns_rdataset_disassociate(&dsset);
+ return (true);
+ }
+ }
+ dns_rdataset_disassociate(&dsset);
+ }
+
+ if (keynode != NULL) {
+ dns_keytable_detachkeynode(keytable, &keynode);
+ }
+
+ dns_keytable_detach(&keytable);
+
+ return (false);
+}
+
+/*%
+ * Check if a root key sentinel SERVFAIL should be returned.
+ */
+static bool
+root_key_sentinel_return_servfail(query_ctx_t *qctx, isc_result_t result) {
+ /*
+ * Are we looking at a "root-key-sentinel" query?
+ */
+ if (!qctx->client->query.root_key_sentinel_is_ta &&
+ !qctx->client->query.root_key_sentinel_not_ta)
+ {
+ return (false);
+ }
+
+ /*
+ * We only care about the query if 'result' indicates we have a cached
+ * answer.
+ */
+ switch (result) {
+ case ISC_R_SUCCESS:
+ case DNS_R_CNAME:
+ case DNS_R_DNAME:
+ case DNS_R_NCACHENXDOMAIN:
+ case DNS_R_NCACHENXRRSET:
+ break;
+ default:
+ return (false);
+ }
+
+ /*
+ * Do we meet the specified conditions to return SERVFAIL?
+ */
+ if (!qctx->is_zone && qctx->rdataset->trust == dns_trust_secure &&
+ ((qctx->client->query.root_key_sentinel_is_ta && !has_ta(qctx)) ||
+ (qctx->client->query.root_key_sentinel_not_ta && has_ta(qctx))))
+ {
+ return (true);
+ }
+
+ /*
+ * As special processing may only be triggered by the original QNAME,
+ * disable it after following a CNAME/DNAME.
+ */
+ qctx->client->query.root_key_sentinel_is_ta = false;
+ qctx->client->query.root_key_sentinel_not_ta = false;
+
+ return (false);
+}
+
+/*%
+ * If serving stale answers is allowed, set up 'qctx' to look for one and
+ * return true; otherwise, return false.
+ */
+static bool
+query_usestale(query_ctx_t *qctx, isc_result_t result) {
+ if ((qctx->client->query.dboptions & DNS_DBFIND_STALEOK) != 0) {
+ /*
+ * Query was already using stale, if that didn't work the
+ * last time, it won't work this time either.
+ */
+ return (false);
+ }
+
+ if (qctx->refresh_rrset) {
+ /*
+ * This is a refreshing query, we have already prioritized
+ * stale data, so don't enable serve-stale again.
+ */
+ return (false);
+ }
+
+ if (result == DNS_R_DUPLICATE || result == DNS_R_DROP ||
+ result == ISC_R_ALREADYRUNNING)
+ {
+ /*
+ * Don't enable serve-stale if the result signals a duplicate
+ * query or a query that is being dropped or can't proceed
+ * because of a recursion loop.
+ */
+ return (false);
+ }
+
+ qctx_clean(qctx);
+ qctx_freedata(qctx);
+
+ if (dns_view_staleanswerenabled(qctx->client->view)) {
+ dns_db_attach(qctx->client->view->cachedb, &qctx->db);
+ qctx->version = NULL;
+ qctx->client->query.dboptions |= DNS_DBFIND_STALEOK;
+ if (qctx->client->query.fetch != NULL) {
+ dns_resolver_destroyfetch(&qctx->client->query.fetch);
+ }
+
+ /*
+ * Start the stale-refresh-time window in case there was a
+ * resolver query timeout.
+ */
+ if (qctx->resuming && result == ISC_R_TIMEDOUT) {
+ qctx->client->query.dboptions |= DNS_DBFIND_STALESTART;
+ }
+ return (true);
+ }
+
+ return (false);
+}
+
+/*%
+ * Continue after doing a database lookup or returning from
+ * recursion, and call out to the next function depending on the
+ * result from the search.
+ */
+static isc_result_t
+query_gotanswer(query_ctx_t *qctx, isc_result_t res) {
+ isc_result_t result = res;
+ char errmsg[256];
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_gotanswer");
+
+ CALL_HOOK(NS_QUERY_GOT_ANSWER_BEGIN, qctx);
+
+ if (query_checkrrl(qctx, result) != ISC_R_SUCCESS) {
+ return (ns_query_done(qctx));
+ }
+
+ if (!dns_name_equal(qctx->client->query.qname, dns_rootname)) {
+ result = query_checkrpz(qctx, result);
+ if (result == ISC_R_NOTFOUND) {
+ /*
+ * RPZ not configured for this view.
+ */
+ goto root_key_sentinel;
+ }
+ if (RECURSING(qctx->client) && result == DNS_R_DISALLOWED) {
+ /*
+ * We are recursing, and thus RPZ processing is not
+ * allowed at the moment. This could happen on a
+ * "stale-answer-client-timeout" lookup. In this case,
+ * bail out and wait for recursion to complete, as we
+ * we can't perform the RPZ rewrite rules.
+ */
+ return (result);
+ }
+ if (result == ISC_R_COMPLETE) {
+ return (ns_query_done(qctx));
+ }
+ }
+
+root_key_sentinel:
+ /*
+ * If required, handle special "root-key-sentinel-is-ta-<keyid>" and
+ * "root-key-sentinel-not-ta-<keyid>" labels by returning SERVFAIL.
+ */
+ if (root_key_sentinel_return_servfail(qctx, result)) {
+ /*
+ * Don't record this response in the SERVFAIL cache.
+ */
+ qctx->client->attributes |= NS_CLIENTATTR_NOSETFC;
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ return (query_prepresponse(qctx));
+
+ case DNS_R_GLUE:
+ case DNS_R_ZONECUT:
+ INSIST(qctx->is_zone);
+ qctx->authoritative = false;
+ return (query_prepresponse(qctx));
+
+ case ISC_R_NOTFOUND:
+ return (query_notfound(qctx));
+
+ case DNS_R_DELEGATION:
+ return (query_delegation(qctx));
+
+ case DNS_R_EMPTYNAME:
+ case DNS_R_NXRRSET:
+ return (query_nodata(qctx, result));
+
+ case DNS_R_EMPTYWILD:
+ case DNS_R_NXDOMAIN:
+ return (query_nxdomain(qctx, result));
+
+ case DNS_R_COVERINGNSEC:
+ return (query_coveringnsec(qctx));
+
+ case DNS_R_NCACHENXDOMAIN:
+ result = query_redirect(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+ return (query_ncache(qctx, DNS_R_NCACHENXDOMAIN));
+
+ case DNS_R_NCACHENXRRSET:
+ return (query_ncache(qctx, DNS_R_NCACHENXRRSET));
+
+ case DNS_R_CNAME:
+ return (query_cname(qctx));
+
+ case DNS_R_DNAME:
+ return (query_dname(qctx));
+
+ default:
+ /*
+ * Something has gone wrong.
+ */
+ snprintf(errmsg, sizeof(errmsg) - 1,
+ "query_gotanswer: unexpected error: %s",
+ isc_result_totext(result));
+ CCTRACE(ISC_LOG_ERROR, errmsg);
+ if (query_usestale(qctx, result)) {
+ /*
+ * If serve-stale is enabled, query_usestale() already
+ * set up 'qctx' for looking up a stale response.
+ */
+ return (query_lookup(qctx));
+ }
+
+ /*
+ * Regardless of the triggering result, we definitely
+ * want to return SERVFAIL from here.
+ */
+ qctx->client->rcode_override = dns_rcode_servfail;
+
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+
+cleanup:
+ return (result);
+}
+
+static void
+query_addnoqnameproof(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ isc_buffer_t *dbuf, b;
+ dns_name_t *fname = NULL;
+ dns_rdataset_t *neg = NULL, *negsig = NULL;
+ isc_result_t result = ISC_R_NOMEMORY;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addnoqnameproof");
+
+ if (qctx->noqname == NULL) {
+ return;
+ }
+
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+
+ fname = ns_client_newname(client, dbuf, &b);
+ neg = ns_client_newrdataset(client);
+ negsig = ns_client_newrdataset(client);
+ if (fname == NULL || neg == NULL || negsig == NULL) {
+ goto cleanup;
+ }
+
+ result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ if ((qctx->noqname->attributes & DNS_RDATASETATTR_CLOSEST) == 0) {
+ goto cleanup;
+ }
+
+ if (fname == NULL) {
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ }
+
+ if (neg == NULL) {
+ neg = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(neg)) {
+ dns_rdataset_disassociate(neg);
+ }
+
+ if (negsig == NULL) {
+ negsig = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(negsig)) {
+ dns_rdataset_disassociate(negsig);
+ }
+
+ if (fname == NULL || neg == NULL || negsig == NULL) {
+ goto cleanup;
+ }
+ result = dns_rdataset_getclosest(qctx->noqname, fname, neg, negsig);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ query_addrrset(qctx, &fname, &neg, &negsig, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+cleanup:
+ if (neg != NULL) {
+ ns_client_putrdataset(client, &neg);
+ }
+ if (negsig != NULL) {
+ ns_client_putrdataset(client, &negsig);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+}
+
+/*%
+ * Build the response for a query for type ANY.
+ */
+static isc_result_t
+query_respond_any(query_ctx_t *qctx) {
+ bool found = false, hidden = false;
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result = ISC_R_UNSET;
+ dns_rdatatype_t onetype = 0; /* type to use for minimal-any */
+ isc_buffer_t b;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_respond_any");
+
+ CALL_HOOK(NS_QUERY_RESPOND_ANY_BEGIN, qctx);
+
+ result = dns_db_allrdatasets(qctx->db, qctx->node, qctx->version, 0, 0,
+ &rdsiter);
+ if (result != ISC_R_SUCCESS) {
+ CCTRACE(ISC_LOG_ERROR, "query_respond_any: allrdatasets "
+ "failed");
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+
+ /*
+ * Calling query_addrrset() with a non-NULL dbuf is going
+ * to either keep or release the name. We don't want it to
+ * release fname, since we may have to call query_addrrset()
+ * more than once. That means we have to call ns_client_keepname()
+ * now, and pass a NULL dbuf to query_addrrset().
+ *
+ * If we do a query_addrrset() below, we must set qctx->fname to
+ * NULL before leaving this block, otherwise we might try to
+ * cleanup qctx->fname even though we're using it!
+ */
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ qctx->tname = qctx->fname;
+
+ result = dns_rdatasetiter_first(rdsiter);
+ while (result == ISC_R_SUCCESS) {
+ dns_rdatasetiter_current(rdsiter, qctx->rdataset);
+
+ /*
+ * We found an NS RRset; no need to add one later.
+ */
+ if (qctx->qtype == dns_rdatatype_any &&
+ qctx->rdataset->type == dns_rdatatype_ns)
+ {
+ qctx->answer_has_ns = true;
+ }
+
+ /*
+ * Note: if we're in this function, then qctx->type
+ * is guaranteed to be ANY, but qctx->qtype (i.e. the
+ * original type requested) might have been RRSIG or
+ * SIG; we need to check for that.
+ */
+ if (qctx->is_zone && qctx->qtype == dns_rdatatype_any &&
+ !dns_db_issecure(qctx->db) &&
+ dns_rdatatype_isdnssec(qctx->rdataset->type))
+ {
+ /*
+ * The zone may be transitioning from insecure
+ * to secure. Hide DNSSEC records from ANY queries.
+ */
+ dns_rdataset_disassociate(qctx->rdataset);
+ hidden = true;
+ } else if (qctx->view->minimal_any && !TCP(qctx->client) &&
+ !WANTDNSSEC(qctx->client) &&
+ qctx->qtype == dns_rdatatype_any &&
+ (qctx->rdataset->type == dns_rdatatype_sig ||
+ qctx->rdataset->type == dns_rdatatype_rrsig))
+ {
+ CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: "
+ "minimal-any skip signature");
+ dns_rdataset_disassociate(qctx->rdataset);
+ } else if (qctx->view->minimal_any && !TCP(qctx->client) &&
+ onetype != 0 && qctx->rdataset->type != onetype &&
+ qctx->rdataset->covers != onetype)
+ {
+ CCTRACE(ISC_LOG_DEBUG(5), "query_respond_any: "
+ "minimal-any skip rdataset");
+ dns_rdataset_disassociate(qctx->rdataset);
+ } else if ((qctx->qtype == dns_rdatatype_any ||
+ qctx->rdataset->type == qctx->qtype) &&
+ qctx->rdataset->type != 0)
+ {
+ if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client))
+ {
+ qctx->noqname = qctx->rdataset;
+ } else {
+ qctx->noqname = NULL;
+ }
+
+ qctx->rpz_st = qctx->client->query.rpz_st;
+ if (qctx->rpz_st != NULL) {
+ qctx->rdataset->ttl =
+ ISC_MIN(qctx->rdataset->ttl,
+ qctx->rpz_st->m.ttl);
+ }
+
+ if (!qctx->is_zone && RECURSIONOK(qctx->client)) {
+ dns_name_t *name;
+ name = (qctx->fname != NULL) ? qctx->fname
+ : qctx->tname;
+ query_prefetch(qctx->client, name,
+ qctx->rdataset);
+ }
+
+ /*
+ * Remember the first RRtype we find so we
+ * can skip others with minimal-any.
+ */
+ if (qctx->rdataset->type == dns_rdatatype_sig ||
+ qctx->rdataset->type == dns_rdatatype_rrsig)
+ {
+ onetype = qctx->rdataset->covers;
+ } else {
+ onetype = qctx->rdataset->type;
+ }
+
+ query_addrrset(qctx,
+ (qctx->fname != NULL) ? &qctx->fname
+ : &qctx->tname,
+ &qctx->rdataset, NULL, NULL,
+ DNS_SECTION_ANSWER);
+
+ query_addnoqnameproof(qctx);
+
+ found = true;
+ INSIST(qctx->tname != NULL);
+
+ /*
+ * rdataset is non-NULL only in certain
+ * pathological cases involving DNAMEs.
+ */
+ if (qctx->rdataset != NULL) {
+ ns_client_putrdataset(qctx->client,
+ &qctx->rdataset);
+ }
+
+ qctx->rdataset = ns_client_newrdataset(qctx->client);
+ if (qctx->rdataset == NULL) {
+ break;
+ }
+ } else {
+ /*
+ * We're not interested in this rdataset.
+ */
+ dns_rdataset_disassociate(qctx->rdataset);
+ }
+
+ result = dns_rdatasetiter_next(rdsiter);
+ }
+
+ dns_rdatasetiter_destroy(&rdsiter);
+
+ if (result != ISC_R_NOMORE) {
+ CCTRACE(ISC_LOG_ERROR, "query_respond_any: rdataset iterator "
+ "failed");
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ return (ns_query_done(qctx));
+ }
+
+ if (found) {
+ /*
+ * Call hook if any answers were found.
+ * Do this before releasing qctx->fname, in case
+ * the hook function needs it.
+ */
+ CALL_HOOK(NS_QUERY_RESPOND_ANY_FOUND, qctx);
+ }
+
+ if (qctx->fname != NULL) {
+ dns_message_puttempname(qctx->client->message, &qctx->fname);
+ }
+
+ if (found) {
+ /*
+ * At least one matching rdataset was found
+ */
+ query_addauth(qctx);
+ } else if (qctx->qtype == dns_rdatatype_rrsig ||
+ qctx->qtype == dns_rdatatype_sig)
+ {
+ /*
+ * No matching rdatasets were found, but we got
+ * here on a search for RRSIG/SIG, so that's okay.
+ */
+ if (!qctx->is_zone) {
+ qctx->authoritative = false;
+ qctx->client->attributes &= ~NS_CLIENTATTR_RA;
+ query_addauth(qctx);
+ return (ns_query_done(qctx));
+ }
+
+ if (qctx->qtype == dns_rdatatype_rrsig &&
+ dns_db_issecure(qctx->db))
+ {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ dns_name_format(qctx->client->query.qname, namebuf,
+ sizeof(namebuf));
+ ns_client_log(qctx->client, DNS_LOGCATEGORY_DNSSEC,
+ NS_LOGMODULE_QUERY, ISC_LOG_WARNING,
+ "missing signature for %s", namebuf);
+ }
+
+ qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b);
+ return (query_sign_nodata(qctx));
+ } else if (!hidden) {
+ /*
+ * No matching rdatasets were found and nothing was
+ * deliberately hidden: something must have gone wrong.
+ */
+ QUERY_ERROR(qctx, DNS_R_SERVFAIL);
+ }
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Set the expire time, if requested, when answering from a secondary,
+ * mirror, or primary zone.
+ */
+static void
+query_getexpire(query_ctx_t *qctx) {
+ dns_zone_t *raw = NULL, *mayberaw;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_getexpire");
+
+ if (qctx->zone == NULL || !qctx->is_zone ||
+ qctx->qtype != dns_rdatatype_soa ||
+ qctx->client->query.restarts != 0 ||
+ (qctx->client->attributes & NS_CLIENTATTR_WANTEXPIRE) == 0)
+ {
+ return;
+ }
+
+ dns_zone_getraw(qctx->zone, &raw);
+ mayberaw = (raw != NULL) ? raw : qctx->zone;
+
+ if (dns_zone_gettype(mayberaw) == dns_zone_secondary ||
+ dns_zone_gettype(mayberaw) == dns_zone_mirror)
+ {
+ isc_time_t expiretime;
+ uint32_t secs;
+ dns_zone_getexpiretime(qctx->zone, &expiretime);
+ secs = isc_time_seconds(&expiretime);
+ if (secs >= qctx->client->now && qctx->result == ISC_R_SUCCESS)
+ {
+ qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE;
+ qctx->client->expire = secs - qctx->client->now;
+ }
+ } else if (dns_zone_gettype(mayberaw) == dns_zone_primary) {
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_soa_t soa;
+
+ result = dns_rdataset_first(qctx->rdataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ dns_rdataset_current(qctx->rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ qctx->client->expire = soa.expire;
+ qctx->client->attributes |= NS_CLIENTATTR_HAVEEXPIRE;
+ }
+
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+}
+
+/*%
+ * Fill the ANSWER section of a positive response.
+ */
+static isc_result_t
+query_addanswer(query_ctx_t *qctx) {
+ dns_rdataset_t **sigrdatasetp = NULL;
+ isc_result_t result = ISC_R_UNSET;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_addanswer");
+
+ CALL_HOOK(NS_QUERY_ADDANSWER_BEGIN, qctx);
+
+ /*
+ * On normal lookups, clear any rdatasets that were added on a
+ * lookup due to stale-answer-client-timeout. Do not clear if we
+ * are going to refresh the RRset, because the stale contents are
+ * prioritized.
+ */
+ if (QUERY_STALEOK(&qctx->client->query) &&
+ !QUERY_STALETIMEOUT(&qctx->client->query) && !qctx->refresh_rrset)
+ {
+ CCTRACE(ISC_LOG_DEBUG(3), "query_clear_stale");
+ query_clear_stale(qctx->client);
+ /*
+ * We can clear the attribute to prevent redundant clearing
+ * in subsequent lookups.
+ */
+ qctx->client->query.attributes &= ~NS_QUERYATTR_STALEOK;
+ }
+
+ if (qctx->dns64) {
+ result = query_dns64(qctx);
+ qctx->noqname = NULL;
+ dns_rdataset_disassociate(qctx->rdataset);
+ dns_message_puttemprdataset(qctx->client->message,
+ &qctx->rdataset);
+ if (result == ISC_R_NOMORE) {
+#ifndef dns64_bis_return_excluded_addresses
+ if (qctx->dns64_exclude) {
+ if (!qctx->is_zone) {
+ return (ns_query_done(qctx));
+ }
+ /*
+ * Add a fake SOA record.
+ */
+ (void)query_addsoa(qctx, 600,
+ DNS_SECTION_AUTHORITY);
+ return (ns_query_done(qctx));
+ }
+#endif /* ifndef dns64_bis_return_excluded_addresses */
+ if (qctx->is_zone) {
+ return (query_nodata(qctx, DNS_R_NXDOMAIN));
+ } else {
+ return (query_ncache(qctx, DNS_R_NXDOMAIN));
+ }
+ } else if (result != ISC_R_SUCCESS) {
+ qctx->result = result;
+ return (ns_query_done(qctx));
+ }
+ } else if (qctx->client->query.dns64_aaaaok != NULL) {
+ query_filter64(qctx);
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ } else {
+ if (!qctx->is_zone && RECURSIONOK(qctx->client) &&
+ !QUERY_STALETIMEOUT(&qctx->client->query))
+ {
+ query_prefetch(qctx->client, qctx->fname,
+ qctx->rdataset);
+ }
+ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) {
+ sigrdatasetp = &qctx->sigrdataset;
+ }
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ sigrdatasetp, qctx->dbuf, DNS_SECTION_ANSWER);
+ }
+
+ return (ISC_R_COMPLETE);
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Build a response for a "normal" query, for a type other than ANY,
+ * for which we have an answer (either positive or negative).
+ */
+static isc_result_t
+query_respond(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_respond");
+
+ /*
+ * Check to see if the AAAA RRset has non-excluded addresses
+ * in it. If not look for a A RRset.
+ */
+ INSIST(qctx->client->query.dns64_aaaaok == NULL);
+
+ if (qctx->qtype == dns_rdatatype_aaaa && !qctx->dns64_exclude &&
+ !ISC_LIST_EMPTY(qctx->view->dns64) &&
+ qctx->client->message->rdclass == dns_rdataclass_in &&
+ !dns64_aaaaok(qctx->client, qctx->rdataset, qctx->sigrdataset))
+ {
+ /*
+ * Look to see if there are A records for this name.
+ */
+ qctx->client->query.dns64_ttl = qctx->rdataset->ttl;
+ SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset);
+ SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset);
+ ns_client_releasename(qctx->client, &qctx->fname);
+ dns_db_detachnode(qctx->db, &qctx->node);
+ qctx->type = qctx->qtype = dns_rdatatype_a;
+ qctx->dns64_exclude = qctx->dns64 = true;
+
+ return (query_lookup(qctx));
+ }
+
+ /*
+ * XXX: This hook is meant to be at the top of this function,
+ * but is postponed until after DNS64 in order to avoid an
+ * assertion if the hook causes recursion. (When DNS64 also
+ * becomes a plugin, it will be necessary to find some
+ * other way to prevent that assertion, since the order in
+ * which plugins are configured can't be enforced.)
+ */
+ CALL_HOOK(NS_QUERY_RESPOND_BEGIN, qctx);
+
+ if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) {
+ qctx->noqname = qctx->rdataset;
+ } else {
+ qctx->noqname = NULL;
+ }
+
+ /*
+ * Special case NS handling
+ */
+ if (qctx->is_zone && qctx->qtype == dns_rdatatype_ns) {
+ /*
+ * We've already got an NS, no need to add one in
+ * the authority section
+ */
+ if (dns_name_equal(qctx->client->query.qname,
+ dns_db_origin(qctx->db)))
+ {
+ qctx->answer_has_ns = true;
+ }
+
+ /*
+ * Always add glue for root priming queries, regardless
+ * of "minimal-responses" setting.
+ */
+ if (dns_name_equal(qctx->client->query.qname, dns_rootname)) {
+ qctx->client->query.attributes &=
+ ~NS_QUERYATTR_NOADDITIONAL;
+ dns_db_attach(qctx->db, &qctx->client->query.gluedb);
+ }
+ }
+
+ /*
+ * Set expire time
+ */
+ query_getexpire(qctx);
+
+ result = query_addanswer(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+
+ query_addnoqnameproof(qctx);
+
+ /*
+ * 'qctx->rdataset' will only be non-NULL here if the ANSWER section of
+ * the message to be sent to the client already contains an RRset with
+ * the same owner name and the same type as 'qctx->rdataset'. This
+ * should never happen, with one exception: when chasing DNAME records,
+ * one of the DNAME records placed in the ANSWER section may turn out
+ * to be the final answer to the client's query, but we have no way of
+ * knowing that until now. In such a case, 'qctx->rdataset' will be
+ * freed later, so we do not need to free it here.
+ */
+ INSIST(qctx->rdataset == NULL || qctx->qtype == dns_rdatatype_dname);
+
+ query_addauth(qctx);
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+static isc_result_t
+query_dns64(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ dns_aclenv_t *env = client->manager->aclenv;
+ dns_name_t *name, *mname;
+ dns_rdata_t *dns64_rdata;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatalist_t *dns64_rdatalist;
+ dns_rdataset_t *dns64_rdataset;
+ dns_rdataset_t *mrdataset;
+ isc_buffer_t *buffer;
+ isc_region_t r;
+ isc_result_t result;
+ dns_view_t *view = client->view;
+ isc_netaddr_t netaddr;
+ dns_dns64_t *dns64;
+ unsigned int flags = 0;
+ const dns_section_t section = DNS_SECTION_ANSWER;
+
+ /*%
+ * To the current response for 'qctx->client', add the answer RRset
+ * '*rdatasetp' and an optional signature set '*sigrdatasetp', with
+ * owner name '*namep', to the answer section, unless they are
+ * already there. Also add any pertinent additional data.
+ *
+ * If 'qctx->dbuf' is not NULL, then 'qctx->fname' is the name
+ * whose data is stored 'qctx->dbuf'. In this case,
+ * query_addrrset() guarantees that when it returns the name
+ * will either have been kept or released.
+ */
+ CTRACE(ISC_LOG_DEBUG(3), "query_dns64");
+
+ qctx->qtype = qctx->type = dns_rdatatype_aaaa;
+
+ name = qctx->fname;
+ mname = NULL;
+ mrdataset = NULL;
+ buffer = NULL;
+ dns64_rdata = NULL;
+ dns64_rdataset = NULL;
+ dns64_rdatalist = NULL;
+ result = dns_message_findname(
+ client->message, section, name, dns_rdatatype_aaaa,
+ qctx->rdataset->covers, &mname, &mrdataset);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We've already got an RRset of the given name and type.
+ * There's nothing else to do;
+ */
+ CTRACE(ISC_LOG_DEBUG(3), "query_dns64: dns_message_findname "
+ "succeeded: done");
+ if (qctx->dbuf != NULL) {
+ ns_client_releasename(client, &qctx->fname);
+ }
+ return (ISC_R_SUCCESS);
+ } else if (result == DNS_R_NXDOMAIN) {
+ /*
+ * The name doesn't exist.
+ */
+ if (qctx->dbuf != NULL) {
+ ns_client_keepname(client, name, qctx->dbuf);
+ }
+ dns_message_addname(client->message, name, section);
+ qctx->fname = NULL;
+ mname = name;
+ } else {
+ RUNTIME_CHECK(result == DNS_R_NXRRSET);
+ if (qctx->dbuf != NULL) {
+ ns_client_releasename(client, &qctx->fname);
+ }
+ }
+
+ if (qctx->rdataset->trust != dns_trust_secure) {
+ client->query.attributes &= ~NS_QUERYATTR_SECURE;
+ }
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+
+ isc_buffer_allocate(client->mctx, &buffer,
+ view->dns64cnt * 16 *
+ dns_rdataset_count(qctx->rdataset));
+ result = dns_message_gettemprdataset(client->message, &dns64_rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdatalist(client->message,
+ &dns64_rdatalist);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_rdatalist_init(dns64_rdatalist);
+ dns64_rdatalist->rdclass = dns_rdataclass_in;
+ dns64_rdatalist->type = dns_rdatatype_aaaa;
+ if (client->query.dns64_ttl != UINT32_MAX) {
+ dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl,
+ client->query.dns64_ttl);
+ } else {
+ dns64_rdatalist->ttl = ISC_MIN(qctx->rdataset->ttl, 600);
+ }
+
+ if (RECURSIONOK(client)) {
+ flags |= DNS_DNS64_RECURSIVE;
+ }
+
+ /*
+ * We use the signatures from the A lookup to set DNS_DNS64_DNSSEC
+ * as this provides a easy way to see if the answer was signed.
+ */
+ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL &&
+ dns_rdataset_isassociated(qctx->sigrdataset))
+ {
+ flags |= DNS_DNS64_DNSSEC;
+ }
+
+ for (result = dns_rdataset_first(qctx->rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(qctx->rdataset))
+ {
+ for (dns64 = ISC_LIST_HEAD(client->view->dns64); dns64 != NULL;
+ dns64 = dns_dns64_next(dns64))
+ {
+ dns_rdataset_current(qctx->rdataset, &rdata);
+ isc_buffer_availableregion(buffer, &r);
+ INSIST(r.length >= 16);
+ result = dns_dns64_aaaafroma(dns64, &netaddr,
+ client->signer, env, flags,
+ rdata.data, r.base);
+ if (result != ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ continue;
+ }
+ isc_buffer_add(buffer, 16);
+ isc_buffer_remainingregion(buffer, &r);
+ isc_buffer_forward(buffer, 16);
+ result = dns_message_gettemprdata(client->message,
+ &dns64_rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdata_init(dns64_rdata);
+ dns_rdata_fromregion(dns64_rdata, dns_rdataclass_in,
+ dns_rdatatype_aaaa, &r);
+ ISC_LIST_APPEND(dns64_rdatalist->rdata, dns64_rdata,
+ link);
+ dns64_rdata = NULL;
+ dns_rdata_reset(&rdata);
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ if (ISC_LIST_EMPTY(dns64_rdatalist->rdata)) {
+ goto cleanup;
+ }
+
+ result = dns_rdatalist_tordataset(dns64_rdatalist, dns64_rdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdataset_setownercase(dns64_rdataset, mname);
+ client->query.attributes |= NS_QUERYATTR_NOADDITIONAL;
+ dns64_rdataset->trust = qctx->rdataset->trust;
+
+ query_addtoname(mname, dns64_rdataset);
+ query_setorder(qctx, mname, dns64_rdataset);
+
+ dns64_rdataset = NULL;
+ dns64_rdatalist = NULL;
+ dns_message_takebuffer(client->message, &buffer);
+ inc_stats(client, ns_statscounter_dns64);
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (buffer != NULL) {
+ isc_buffer_free(&buffer);
+ }
+
+ if (dns64_rdata != NULL) {
+ dns_message_puttemprdata(client->message, &dns64_rdata);
+ }
+
+ if (dns64_rdataset != NULL) {
+ dns_message_puttemprdataset(client->message, &dns64_rdataset);
+ }
+
+ if (dns64_rdatalist != NULL) {
+ for (dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata);
+ dns64_rdata != NULL;
+ dns64_rdata = ISC_LIST_HEAD(dns64_rdatalist->rdata))
+ {
+ ISC_LIST_UNLINK(dns64_rdatalist->rdata, dns64_rdata,
+ link);
+ dns_message_puttemprdata(client->message, &dns64_rdata);
+ }
+ dns_message_puttemprdatalist(client->message, &dns64_rdatalist);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_dns64: done");
+ return (result);
+}
+
+static void
+query_filter64(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ dns_name_t *name, *mname;
+ dns_rdata_t *myrdata;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdatalist_t *myrdatalist;
+ dns_rdataset_t *myrdataset;
+ isc_buffer_t *buffer;
+ isc_region_t r;
+ isc_result_t result;
+ unsigned int i;
+ const dns_section_t section = DNS_SECTION_ANSWER;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_filter64");
+
+ INSIST(client->query.dns64_aaaaok != NULL);
+ INSIST(client->query.dns64_aaaaoklen ==
+ dns_rdataset_count(qctx->rdataset));
+
+ name = qctx->fname;
+ mname = NULL;
+ buffer = NULL;
+ myrdata = NULL;
+ myrdataset = NULL;
+ myrdatalist = NULL;
+ result = dns_message_findname(
+ client->message, section, name, dns_rdatatype_aaaa,
+ qctx->rdataset->covers, &mname, &myrdataset);
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * We've already got an RRset of the given name and type.
+ * There's nothing else to do;
+ */
+ CTRACE(ISC_LOG_DEBUG(3), "query_filter64: dns_message_findname "
+ "succeeded: done");
+ if (qctx->dbuf != NULL) {
+ ns_client_releasename(client, &qctx->fname);
+ }
+ return;
+ } else if (result == DNS_R_NXDOMAIN) {
+ mname = name;
+ qctx->fname = NULL;
+ } else {
+ RUNTIME_CHECK(result == DNS_R_NXRRSET);
+ if (qctx->dbuf != NULL) {
+ ns_client_releasename(client, &qctx->fname);
+ }
+ qctx->dbuf = NULL;
+ }
+
+ if (qctx->rdataset->trust != dns_trust_secure) {
+ client->query.attributes &= ~NS_QUERYATTR_SECURE;
+ }
+
+ isc_buffer_allocate(client->mctx, &buffer,
+ 16 * dns_rdataset_count(qctx->rdataset));
+ result = dns_message_gettemprdataset(client->message, &myrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ result = dns_message_gettemprdatalist(client->message, &myrdatalist);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ dns_rdatalist_init(myrdatalist);
+ myrdatalist->rdclass = dns_rdataclass_in;
+ myrdatalist->type = dns_rdatatype_aaaa;
+ myrdatalist->ttl = qctx->rdataset->ttl;
+
+ i = 0;
+ for (result = dns_rdataset_first(qctx->rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(qctx->rdataset))
+ {
+ if (!client->query.dns64_aaaaok[i++]) {
+ continue;
+ }
+ dns_rdataset_current(qctx->rdataset, &rdata);
+ INSIST(rdata.length == 16);
+ isc_buffer_putmem(buffer, rdata.data, rdata.length);
+ isc_buffer_remainingregion(buffer, &r);
+ isc_buffer_forward(buffer, rdata.length);
+ result = dns_message_gettemprdata(client->message, &myrdata);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdata_init(myrdata);
+ dns_rdata_fromregion(myrdata, dns_rdataclass_in,
+ dns_rdatatype_aaaa, &r);
+ ISC_LIST_APPEND(myrdatalist->rdata, myrdata, link);
+ myrdata = NULL;
+ dns_rdata_reset(&rdata);
+ }
+ if (result != ISC_R_NOMORE) {
+ goto cleanup;
+ }
+
+ result = dns_rdatalist_tordataset(myrdatalist, myrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ dns_rdataset_setownercase(myrdataset, name);
+ client->query.attributes |= NS_QUERYATTR_NOADDITIONAL;
+ if (mname == name) {
+ if (qctx->dbuf != NULL) {
+ ns_client_keepname(client, name, qctx->dbuf);
+ }
+ dns_message_addname(client->message, name, section);
+ qctx->dbuf = NULL;
+ }
+ myrdataset->trust = qctx->rdataset->trust;
+
+ query_addtoname(mname, myrdataset);
+ query_setorder(qctx, mname, myrdataset);
+
+ myrdataset = NULL;
+ myrdatalist = NULL;
+ dns_message_takebuffer(client->message, &buffer);
+
+cleanup:
+ if (buffer != NULL) {
+ isc_buffer_free(&buffer);
+ }
+
+ if (myrdata != NULL) {
+ dns_message_puttemprdata(client->message, &myrdata);
+ }
+
+ if (myrdataset != NULL) {
+ dns_message_puttemprdataset(client->message, &myrdataset);
+ }
+
+ if (myrdatalist != NULL) {
+ for (myrdata = ISC_LIST_HEAD(myrdatalist->rdata);
+ myrdata != NULL;
+ myrdata = ISC_LIST_HEAD(myrdatalist->rdata))
+ {
+ ISC_LIST_UNLINK(myrdatalist->rdata, myrdata, link);
+ dns_message_puttemprdata(client->message, &myrdata);
+ }
+ dns_message_puttemprdatalist(client->message, &myrdatalist);
+ }
+ if (qctx->dbuf != NULL) {
+ ns_client_releasename(client, &name);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_filter64: done");
+}
+
+/*%
+ * Handle the case of a name not being found in a database lookup.
+ * Called from query_gotanswer(). Passes off processing to
+ * query_delegation() for a root referral if appropriate.
+ */
+static isc_result_t
+query_notfound(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_notfound");
+
+ CALL_HOOK(NS_QUERY_NOTFOUND_BEGIN, qctx);
+
+ INSIST(!qctx->is_zone);
+
+ if (qctx->db != NULL) {
+ dns_db_detach(&qctx->db);
+ }
+
+ /*
+ * If the cache doesn't even have the root NS,
+ * try to get that from the hints DB.
+ */
+ if (qctx->view->hints != NULL) {
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, qctx->client, NULL);
+
+ dns_db_attach(qctx->view->hints, &qctx->db);
+ result = dns_db_findext(qctx->db, dns_rootname, NULL,
+ dns_rdatatype_ns, 0, qctx->client->now,
+ &qctx->node, qctx->fname, &cm, &ci,
+ qctx->rdataset, qctx->sigrdataset);
+ } else {
+ /* We have no hints. */
+ result = ISC_R_FAILURE;
+ }
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Nonsensical root hints may require cleanup.
+ */
+ qctx_clean(qctx);
+
+ /*
+ * We don't have any root server hints, but
+ * we may have working forwarders, so try to
+ * recurse anyway.
+ */
+ if (RECURSIONOK(qctx->client)) {
+ INSIST(!REDIRECT(qctx->client));
+ result = ns_query_recurse(qctx->client, qctx->qtype,
+ qctx->client->query.qname,
+ NULL, NULL, qctx->resuming);
+ if (result == ISC_R_SUCCESS) {
+ CALL_HOOK(NS_QUERY_NOTFOUND_RECURSE, qctx);
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_RECURSING;
+
+ if (qctx->dns64) {
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_DNS64;
+ }
+ if (qctx->dns64_exclude) {
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_DNS64EXCLUDE;
+ }
+ } else if (query_usestale(qctx, result)) {
+ /*
+ * If serve-stale is enabled, query_usestale()
+ * already set up 'qctx' for looking up a
+ * stale response.
+ */
+ return (query_lookup(qctx));
+ } else {
+ QUERY_ERROR(qctx, result);
+ }
+ return (ns_query_done(qctx));
+ } else {
+ /* Unable to give root server referral. */
+ CCTRACE(ISC_LOG_ERROR, "unable to give root server "
+ "referral");
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+ }
+
+ return (query_delegation(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * We have a delegation but recursion is not allowed, so return the delegation
+ * to the client.
+ */
+static isc_result_t
+query_prepare_delegation_response(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_rdataset_t **sigrdatasetp = NULL;
+ bool detach = false;
+
+ CALL_HOOK(NS_QUERY_PREP_DELEGATION_BEGIN, qctx);
+
+ /*
+ * qctx->fname could be released in query_addrrset(), so save a copy of
+ * it here in case we need it.
+ */
+ dns_fixedname_init(&qctx->dsname);
+ dns_name_copy(qctx->fname, dns_fixedname_name(&qctx->dsname));
+
+ /*
+ * This is the best answer.
+ */
+ qctx->client->query.isreferral = true;
+
+ if (!dns_db_iscache(qctx->db) && qctx->client->query.gluedb == NULL) {
+ dns_db_attach(qctx->db, &qctx->client->query.gluedb);
+ detach = true;
+ }
+
+ /*
+ * We must ensure NOADDITIONAL is off, because the generation of
+ * additional data is required in delegations.
+ */
+ qctx->client->query.attributes &= ~NS_QUERYATTR_NOADDITIONAL;
+ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) {
+ sigrdatasetp = &qctx->sigrdataset;
+ }
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp,
+ qctx->dbuf, DNS_SECTION_AUTHORITY);
+ if (detach) {
+ dns_db_detach(&qctx->client->query.gluedb);
+ }
+
+ /*
+ * Add DS/NSEC(3) record(s) if needed.
+ */
+ query_addds(qctx);
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Handle a delegation response from an authoritative lookup. This
+ * may trigger additional lookups, e.g. from the cache database to
+ * see if we have a better answer; if that is not allowed, return the
+ * delegation to the client and call ns_query_done().
+ */
+static isc_result_t
+query_zone_delegation(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+
+ CALL_HOOK(NS_QUERY_ZONE_DELEGATION_BEGIN, qctx);
+
+ /*
+ * If the query type is DS, look to see if we are
+ * authoritative for the child zone
+ */
+ if (!RECURSIONOK(qctx->client) &&
+ (qctx->options & DNS_GETDB_NOEXACT) != 0 &&
+ qctx->qtype == dns_rdatatype_ds)
+ {
+ dns_db_t *tdb = NULL;
+ dns_zone_t *tzone = NULL;
+ dns_dbversion_t *tversion = NULL;
+ result = query_getzonedb(
+ qctx->client, qctx->client->query.qname, qctx->qtype,
+ DNS_GETDB_PARTIAL, &tzone, &tdb, &tversion);
+ if (result != ISC_R_SUCCESS) {
+ if (tdb != NULL) {
+ dns_db_detach(&tdb);
+ }
+ if (tzone != NULL) {
+ dns_zone_detach(&tzone);
+ }
+ } else {
+ qctx->options &= ~DNS_GETDB_NOEXACT;
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ if (qctx->sigrdataset != NULL) {
+ ns_client_putrdataset(qctx->client,
+ &qctx->sigrdataset);
+ }
+ if (qctx->fname != NULL) {
+ ns_client_releasename(qctx->client,
+ &qctx->fname);
+ }
+ if (qctx->node != NULL) {
+ dns_db_detachnode(qctx->db, &qctx->node);
+ }
+ if (qctx->db != NULL) {
+ dns_db_detach(&qctx->db);
+ }
+ if (qctx->zone != NULL) {
+ dns_zone_detach(&qctx->zone);
+ }
+ qctx->version = NULL;
+ RESTORE(qctx->version, tversion);
+ RESTORE(qctx->db, tdb);
+ RESTORE(qctx->zone, tzone);
+ qctx->authoritative = true;
+
+ return (query_lookup(qctx));
+ }
+ }
+
+ if (USECACHE(qctx->client) &&
+ (RECURSIONOK(qctx->client) ||
+ (qctx->zone != NULL &&
+ dns_zone_gettype(qctx->zone) == dns_zone_mirror)))
+ {
+ /*
+ * We might have a better answer or delegation in the
+ * cache. We'll remember the current values of fname,
+ * rdataset, and sigrdataset. We'll then go looking for
+ * QNAME in the cache. If we find something better, we'll
+ * use it instead. If not, then query_lookup() calls
+ * query_notfound() which calls query_delegation(), and
+ * we'll restore these values there.
+ */
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ SAVE(qctx->zdb, qctx->db);
+ SAVE(qctx->znode, qctx->node);
+ SAVE(qctx->zfname, qctx->fname);
+ SAVE(qctx->zversion, qctx->version);
+ SAVE(qctx->zrdataset, qctx->rdataset);
+ SAVE(qctx->zsigrdataset, qctx->sigrdataset);
+ dns_db_attach(qctx->view->cachedb, &qctx->db);
+ qctx->is_zone = false;
+
+ return (query_lookup(qctx));
+ }
+
+ return (query_prepare_delegation_response(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Handle delegation responses, including root referrals.
+ *
+ * If the delegation was returned from authoritative data,
+ * call query_zone_delgation(). Otherwise, we can start
+ * recursion if allowed; or else return the delegation to the
+ * client and call ns_query_done().
+ */
+static isc_result_t
+query_delegation(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_delegation");
+
+ CALL_HOOK(NS_QUERY_DELEGATION_BEGIN, qctx);
+
+ qctx->authoritative = false;
+
+ if (qctx->is_zone) {
+ return (query_zone_delegation(qctx));
+ }
+
+ if (qctx->zfname != NULL &&
+ (!dns_name_issubdomain(qctx->fname, qctx->zfname) ||
+ (qctx->is_staticstub_zone &&
+ dns_name_equal(qctx->fname, qctx->zfname))))
+ {
+ /*
+ * In the following cases use "authoritative"
+ * data instead of the cache delegation:
+ * 1. We've already got a delegation from
+ * authoritative data, and it is better
+ * than what we found in the cache.
+ * (See the comment above.)
+ * 2. The query name matches the origin name
+ * of a static-stub zone. This needs to be
+ * considered for the case where the NS of
+ * the static-stub zone and the cached NS
+ * are different. We still need to contact
+ * the nameservers configured in the
+ * static-stub zone.
+ */
+ ns_client_releasename(qctx->client, &qctx->fname);
+
+ /*
+ * We've already done ns_client_keepname() on
+ * qctx->zfname, so we must set dbuf to NULL to
+ * prevent query_addrrset() from trying to
+ * call ns_client_keepname() again.
+ */
+ qctx->dbuf = NULL;
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ if (qctx->sigrdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->sigrdataset);
+ }
+ qctx->version = NULL;
+
+ dns_db_detachnode(qctx->db, &qctx->node);
+ dns_db_detach(&qctx->db);
+ RESTORE(qctx->db, qctx->zdb);
+ RESTORE(qctx->node, qctx->znode);
+ RESTORE(qctx->fname, qctx->zfname);
+ RESTORE(qctx->version, qctx->zversion);
+ RESTORE(qctx->rdataset, qctx->zrdataset);
+ RESTORE(qctx->sigrdataset, qctx->zsigrdataset);
+ }
+
+ result = query_delegation_recurse(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+
+ return (query_prepare_delegation_response(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Handle recursive queries that are triggered as part of the
+ * delegation process.
+ */
+static isc_result_t
+query_delegation_recurse(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_name_t *qname = qctx->client->query.qname;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_delegation_recurse");
+
+ if (!RECURSIONOK(qctx->client)) {
+ return (ISC_R_COMPLETE);
+ }
+
+ CALL_HOOK(NS_QUERY_DELEGATION_RECURSE_BEGIN, qctx);
+
+ /*
+ * We have a delegation and recursion is allowed,
+ * so we call ns_query_recurse() to follow it.
+ * This phase of the query processing is done;
+ * we'll resume via fetch_callback() and
+ * query_resume() when the recursion is complete.
+ */
+
+ INSIST(!REDIRECT(qctx->client));
+
+ if (dns_rdatatype_atparent(qctx->type)) {
+ /*
+ * Parent is authoritative for this RDATA type (i.e. DS).
+ */
+ result = ns_query_recurse(qctx->client, qctx->qtype, qname,
+ NULL, NULL, qctx->resuming);
+ } else if (qctx->dns64) {
+ /*
+ * Look up an A record so we can synthesize DNS64.
+ */
+ result = ns_query_recurse(qctx->client, dns_rdatatype_a, qname,
+ NULL, NULL, qctx->resuming);
+ } else {
+ /*
+ * Any other recursion.
+ */
+ result = ns_query_recurse(qctx->client, qctx->qtype, qname,
+ qctx->fname, qctx->rdataset,
+ qctx->resuming);
+ }
+
+ if (result == ISC_R_SUCCESS) {
+ qctx->client->query.attributes |= NS_QUERYATTR_RECURSING;
+ if (qctx->dns64) {
+ qctx->client->query.attributes |= NS_QUERYATTR_DNS64;
+ }
+ if (qctx->dns64_exclude) {
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_DNS64EXCLUDE;
+ }
+ } else if (query_usestale(qctx, result)) {
+ /*
+ * If serve-stale is enabled, query_usestale() already set up
+ * 'qctx' for looking up a stale response.
+ */
+ return (query_lookup(qctx));
+ } else {
+ QUERY_ERROR(qctx, result);
+ }
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Add DS/NSEC(3) record(s) if needed.
+ */
+static void
+query_addds(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ dns_fixedname_t fixed;
+ dns_name_t *fname = NULL;
+ dns_name_t *rname = NULL;
+ dns_name_t *name;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ isc_buffer_t *dbuf, b;
+ isc_result_t result;
+ unsigned int count;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addds");
+
+ /*
+ * DS not needed.
+ */
+ if (!WANTDNSSEC(client)) {
+ return;
+ }
+
+ /*
+ * We'll need some resources...
+ */
+ rdataset = ns_client_newrdataset(client);
+ sigrdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL || sigrdataset == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * Look for the DS record, which may or may not be present.
+ */
+ result = dns_db_findrdataset(qctx->db, qctx->node, qctx->version,
+ dns_rdatatype_ds, 0, client->now, rdataset,
+ sigrdataset);
+ /*
+ * If we didn't find it, look for an NSEC.
+ */
+ if (result == ISC_R_NOTFOUND) {
+ result = dns_db_findrdataset(
+ qctx->db, qctx->node, qctx->version, dns_rdatatype_nsec,
+ 0, client->now, rdataset, sigrdataset);
+ }
+ if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+ goto addnsec3;
+ }
+ if (!dns_rdataset_isassociated(rdataset) ||
+ !dns_rdataset_isassociated(sigrdataset))
+ {
+ goto addnsec3;
+ }
+
+ /*
+ * We've already added the NS record, so if the name's not there,
+ * we have other problems.
+ */
+ result = dns_message_firstname(client->message, DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Find the delegation in the response message - it is not necessarily
+ * the first name in the AUTHORITY section when wildcard processing is
+ * involved.
+ */
+ while (result == ISC_R_SUCCESS) {
+ rname = NULL;
+ dns_message_currentname(client->message, DNS_SECTION_AUTHORITY,
+ &rname);
+ result = dns_message_findtype(rname, dns_rdatatype_ns, 0, NULL);
+ if (result == ISC_R_SUCCESS) {
+ break;
+ }
+ result = dns_message_nextname(client->message,
+ DNS_SECTION_AUTHORITY);
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ /*
+ * Add the relevant RRset (DS or NSEC) to the delegation.
+ */
+ query_addrrset(qctx, &rname, &rdataset, &sigrdataset, NULL,
+ DNS_SECTION_AUTHORITY);
+ goto cleanup;
+
+addnsec3:
+ if (!dns_db_iszone(qctx->db)) {
+ goto cleanup;
+ }
+ /*
+ * Add the NSEC3 which proves the DS does not exist.
+ */
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ dns_fixedname_init(&fixed);
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ if (dns_rdataset_isassociated(sigrdataset)) {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+ name = dns_fixedname_name(&qctx->dsname);
+ query_findclosestnsec3(name, qctx->db, qctx->version, client, rdataset,
+ sigrdataset, fname, true,
+ dns_fixedname_name(&fixed));
+ if (!dns_rdataset_isassociated(rdataset)) {
+ goto cleanup;
+ }
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+ /*
+ * Did we find the closest provable encloser instead?
+ * If so add the nearest to the closest provable encloser.
+ */
+ if (!dns_name_equal(name, dns_fixedname_name(&fixed))) {
+ count = dns_name_countlabels(dns_fixedname_name(&fixed)) + 1;
+ dns_name_getlabelsequence(name,
+ dns_name_countlabels(name) - count,
+ count, dns_fixedname_name(&fixed));
+ fixfname(client, &fname, &dbuf, &b);
+ fixrdataset(client, &rdataset);
+ fixrdataset(client, &sigrdataset);
+ if (fname == NULL || rdataset == NULL || sigrdataset == NULL) {
+ goto cleanup;
+ }
+ query_findclosestnsec3(dns_fixedname_name(&fixed), qctx->db,
+ qctx->version, client, rdataset,
+ sigrdataset, fname, false, NULL);
+ if (!dns_rdataset_isassociated(rdataset)) {
+ goto cleanup;
+ }
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+ }
+
+cleanup:
+ if (rdataset != NULL) {
+ ns_client_putrdataset(client, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+}
+
+/*%
+ * Handle authoritative NOERROR/NODATA responses.
+ */
+static isc_result_t
+query_nodata(query_ctx_t *qctx, isc_result_t res) {
+ isc_result_t result = res;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_nodata");
+
+ CALL_HOOK(NS_QUERY_NODATA_BEGIN, qctx);
+
+#ifdef dns64_bis_return_excluded_addresses
+ if (qctx->dns64)
+#else /* ifdef dns64_bis_return_excluded_addresses */
+ if (qctx->dns64 && !qctx->dns64_exclude)
+#endif /* ifdef dns64_bis_return_excluded_addresses */
+ {
+ isc_buffer_t b;
+ /*
+ * Restore the answers from the previous AAAA lookup.
+ */
+ if (qctx->rdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ }
+ if (qctx->sigrdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->sigrdataset);
+ }
+ RESTORE(qctx->rdataset, qctx->client->query.dns64_aaaa);
+ RESTORE(qctx->sigrdataset, qctx->client->query.dns64_sigaaaa);
+ if (qctx->fname == NULL) {
+ qctx->dbuf = ns_client_getnamebuf(qctx->client);
+ if (qctx->dbuf == NULL) {
+ CCTRACE(ISC_LOG_ERROR, "query_nodata: "
+ "ns_client_getnamebuf "
+ "failed (3)");
+ QUERY_ERROR(qctx, ISC_R_NOMEMORY);
+ return (ns_query_done(qctx));
+ }
+ qctx->fname = ns_client_newname(qctx->client,
+ qctx->dbuf, &b);
+ if (qctx->fname == NULL) {
+ CCTRACE(ISC_LOG_ERROR, "query_nodata: "
+ "ns_client_newname "
+ "failed (3)");
+ QUERY_ERROR(qctx, ISC_R_NOMEMORY);
+ return (ns_query_done(qctx));
+ }
+ }
+ dns_name_copy(qctx->client->query.qname, qctx->fname);
+ qctx->dns64 = false;
+#ifdef dns64_bis_return_excluded_addresses
+ /*
+ * Resume the diverted processing of the AAAA response?
+ */
+ if (qctx->dns64_exclude) {
+ return (query_prepresponse(qctx));
+ }
+#endif /* ifdef dns64_bis_return_excluded_addresses */
+ } else if ((result == DNS_R_NXRRSET || result == DNS_R_NCACHENXRRSET) &&
+ !ISC_LIST_EMPTY(qctx->view->dns64) && !qctx->nxrewrite &&
+ qctx->client->message->rdclass == dns_rdataclass_in &&
+ qctx->qtype == dns_rdatatype_aaaa)
+ {
+ /*
+ * Look to see if there are A records for this name.
+ */
+ switch (result) {
+ case DNS_R_NCACHENXRRSET:
+ /*
+ * This is from the negative cache; if the ttl is
+ * zero, we need to work out whether we have just
+ * decremented to zero or there was no negative
+ * cache ttl in the answer.
+ */
+ if (qctx->rdataset->ttl != 0) {
+ qctx->client->query.dns64_ttl =
+ qctx->rdataset->ttl;
+ break;
+ }
+ if (dns_rdataset_first(qctx->rdataset) == ISC_R_SUCCESS)
+ {
+ qctx->client->query.dns64_ttl = 0;
+ }
+ break;
+ case DNS_R_NXRRSET:
+ qctx->client->query.dns64_ttl =
+ dns64_ttl(qctx->db, qctx->version);
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ SAVE(qctx->client->query.dns64_aaaa, qctx->rdataset);
+ SAVE(qctx->client->query.dns64_sigaaaa, qctx->sigrdataset);
+ ns_client_releasename(qctx->client, &qctx->fname);
+ dns_db_detachnode(qctx->db, &qctx->node);
+ qctx->type = qctx->qtype = dns_rdatatype_a;
+ qctx->dns64 = true;
+ return (query_lookup(qctx));
+ }
+
+ if (qctx->is_zone) {
+ return (query_sign_nodata(qctx));
+ } else {
+ /*
+ * We don't call query_addrrset() because we don't need any
+ * of its extra features (and things would probably break!).
+ */
+ if (dns_rdataset_isassociated(qctx->rdataset)) {
+ ns_client_keepname(qctx->client, qctx->fname,
+ qctx->dbuf);
+ dns_message_addname(qctx->client->message, qctx->fname,
+ DNS_SECTION_AUTHORITY);
+ ISC_LIST_APPEND(qctx->fname->list, qctx->rdataset,
+ link);
+ qctx->fname = NULL;
+ qctx->rdataset = NULL;
+ }
+ }
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Add RRSIGs for NOERROR/NODATA responses when answering authoritatively.
+ */
+isc_result_t
+query_sign_nodata(query_ctx_t *qctx) {
+ isc_result_t result;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_sign_nodata");
+
+ /*
+ * Look for a NSEC3 record if we don't have a NSEC record.
+ */
+ if (qctx->redirected) {
+ return (ns_query_done(qctx));
+ }
+ if (!dns_rdataset_isassociated(qctx->rdataset) &&
+ WANTDNSSEC(qctx->client))
+ {
+ if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) {
+ dns_name_t *found;
+ dns_name_t *qname;
+ dns_fixedname_t fixed;
+ isc_buffer_t b;
+
+ found = dns_fixedname_initname(&fixed);
+ qname = qctx->client->query.qname;
+
+ query_findclosestnsec3(qname, qctx->db, qctx->version,
+ qctx->client, qctx->rdataset,
+ qctx->sigrdataset, qctx->fname,
+ true, found);
+ /*
+ * Did we find the closest provable encloser
+ * instead? If so add the nearest to the
+ * closest provable encloser.
+ */
+ if (dns_rdataset_isassociated(qctx->rdataset) &&
+ !dns_name_equal(qname, found) &&
+ (((qctx->client->sctx->options &
+ NS_SERVER_NONEAREST) == 0) ||
+ qctx->qtype == dns_rdatatype_ds))
+ {
+ unsigned int count;
+ unsigned int skip;
+
+ /*
+ * Add the closest provable encloser.
+ */
+ query_addrrset(qctx, &qctx->fname,
+ &qctx->rdataset,
+ &qctx->sigrdataset, qctx->dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ count = dns_name_countlabels(found) + 1;
+ skip = dns_name_countlabels(qname) - count;
+ dns_name_getlabelsequence(qname, skip, count,
+ found);
+
+ fixfname(qctx->client, &qctx->fname,
+ &qctx->dbuf, &b);
+ fixrdataset(qctx->client, &qctx->rdataset);
+ fixrdataset(qctx->client, &qctx->sigrdataset);
+ if (qctx->fname == NULL ||
+ qctx->rdataset == NULL ||
+ qctx->sigrdataset == NULL)
+ {
+ CCTRACE(ISC_LOG_ERROR, "query_sign_"
+ "nodata: "
+ "failure "
+ "getting "
+ "closest "
+ "encloser");
+ QUERY_ERROR(qctx, ISC_R_NOMEMORY);
+ return (ns_query_done(qctx));
+ }
+ /*
+ * 'nearest' doesn't exist so
+ * 'exist' is set to false.
+ */
+ query_findclosestnsec3(
+ found, qctx->db, qctx->version,
+ qctx->client, qctx->rdataset,
+ qctx->sigrdataset, qctx->fname, false,
+ NULL);
+ }
+ } else {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ query_addwildcardproof(qctx, false, true);
+ }
+ }
+ if (dns_rdataset_isassociated(qctx->rdataset)) {
+ /*
+ * If we've got a NSEC record, we need to save the
+ * name now because we're going call query_addsoa()
+ * below, and it needs to use the name buffer.
+ */
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ } else if (qctx->fname != NULL) {
+ /*
+ * We're not going to use fname, and need to release
+ * our hold on the name buffer so query_addsoa()
+ * may use it.
+ */
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ /*
+ * The RPZ SOA has already been added to the additional section
+ * if this was an RPZ rewrite, but if it wasn't, add it now.
+ */
+ if (!qctx->nxrewrite) {
+ result = query_addsoa(qctx, UINT32_MAX, DNS_SECTION_AUTHORITY);
+ if (result != ISC_R_SUCCESS) {
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+ }
+
+ /*
+ * Add NSEC record if we found one.
+ */
+ if (WANTDNSSEC(qctx->client) &&
+ dns_rdataset_isassociated(qctx->rdataset))
+ {
+ query_addnxrrsetnsec(qctx);
+ }
+
+ return (ns_query_done(qctx));
+}
+
+static void
+query_addnxrrsetnsec(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ dns_rdata_t sigrdata;
+ dns_rdata_rrsig_t sig;
+ unsigned int labels;
+ isc_buffer_t *dbuf, b;
+ dns_name_t *fname;
+ isc_result_t result;
+
+ INSIST(qctx->fname != NULL);
+
+ if ((qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) == 0) {
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY);
+ return;
+ }
+
+ if (qctx->sigrdataset == NULL ||
+ !dns_rdataset_isassociated(qctx->sigrdataset))
+ {
+ return;
+ }
+
+ if (dns_rdataset_first(qctx->sigrdataset) != ISC_R_SUCCESS) {
+ return;
+ }
+
+ dns_rdata_init(&sigrdata);
+ dns_rdataset_current(qctx->sigrdataset, &sigrdata);
+ result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ labels = dns_name_countlabels(qctx->fname);
+ if ((unsigned int)sig.labels + 1 >= labels) {
+ return;
+ }
+
+ query_addwildcardproof(qctx, true, false);
+
+ /*
+ * We'll need some resources...
+ */
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ return;
+ }
+
+ fname = ns_client_newname(client, dbuf, &b);
+ if (fname == NULL) {
+ return;
+ }
+
+ dns_name_split(qctx->fname, sig.labels + 1, NULL, fname);
+ /* This will succeed, since we've stripped labels. */
+ RUNTIME_CHECK(dns_name_concatenate(dns_wildcardname, fname, fname,
+ NULL) == ISC_R_SUCCESS);
+ query_addrrset(qctx, &fname, &qctx->rdataset, &qctx->sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+}
+
+/*%
+ * Handle NXDOMAIN and empty wildcard responses.
+ */
+static isc_result_t
+query_nxdomain(query_ctx_t *qctx, isc_result_t res) {
+ dns_section_t section;
+ uint32_t ttl;
+ isc_result_t result = res;
+ bool empty_wild = (res == DNS_R_EMPTYWILD);
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_nxdomain");
+
+ CALL_HOOK(NS_QUERY_NXDOMAIN_BEGIN, qctx);
+
+ INSIST(qctx->is_zone || REDIRECT(qctx->client));
+
+ if (!empty_wild) {
+ result = query_redirect(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+ }
+
+ if (dns_rdataset_isassociated(qctx->rdataset)) {
+ /*
+ * If we've got a NSEC record, we need to save the
+ * name now because we're going call query_addsoa()
+ * below, and it needs to use the name buffer.
+ */
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ } else if (qctx->fname != NULL) {
+ /*
+ * We're not going to use fname, and need to release
+ * our hold on the name buffer so query_addsoa()
+ * may use it.
+ */
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ /*
+ * Add SOA to the additional section if generated by a
+ * RPZ rewrite.
+ *
+ * If the query was for a SOA record force the
+ * ttl to zero so that it is possible for clients to find
+ * the containing zone of an arbitrary name with a stub
+ * resolver and not have it cached.
+ */
+ section = qctx->nxrewrite ? DNS_SECTION_ADDITIONAL
+ : DNS_SECTION_AUTHORITY;
+ ttl = UINT32_MAX;
+ if (!qctx->nxrewrite && qctx->qtype == dns_rdatatype_soa &&
+ qctx->zone != NULL && dns_zone_getzeronosoattl(qctx->zone))
+ {
+ ttl = 0;
+ }
+ if (!qctx->nxrewrite ||
+ (qctx->rpz_st != NULL && qctx->rpz_st->m.rpz->addsoa))
+ {
+ result = query_addsoa(qctx, ttl, section);
+ if (result != ISC_R_SUCCESS) {
+ QUERY_ERROR(qctx, result);
+ return (ns_query_done(qctx));
+ }
+ }
+
+ if (WANTDNSSEC(qctx->client)) {
+ /*
+ * Add NSEC record if we found one.
+ */
+ if (dns_rdataset_isassociated(qctx->rdataset)) {
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ &qctx->sigrdataset, NULL,
+ DNS_SECTION_AUTHORITY);
+ }
+ query_addwildcardproof(qctx, false, false);
+ }
+
+ /*
+ * Set message rcode.
+ */
+ if (empty_wild) {
+ qctx->client->message->rcode = dns_rcode_noerror;
+ } else {
+ qctx->client->message->rcode = dns_rcode_nxdomain;
+ }
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Handle both types of NXDOMAIN redirection, calling redirect()
+ * (which implements type redirect zones) and redirect2() (which
+ * implements recursive nxdomain-redirect lookups).
+ *
+ * Any result code other than ISC_R_COMPLETE means redirection was
+ * successful and the result code should be returned up the call stack.
+ *
+ * ISC_R_COMPLETE means we reached the end of this function without
+ * redirecting, so query processing should continue past it.
+ */
+static isc_result_t
+query_redirect(query_ctx_t *qctx) {
+ isc_result_t result;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_redirect");
+
+ result = redirect(qctx->client, qctx->fname, qctx->rdataset,
+ &qctx->node, &qctx->db, &qctx->version, qctx->type);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ inc_stats(qctx->client, ns_statscounter_nxdomainredirect);
+ return (query_prepresponse(qctx));
+ case DNS_R_NXRRSET:
+ qctx->redirected = true;
+ qctx->is_zone = true;
+ return (query_nodata(qctx, DNS_R_NXRRSET));
+ case DNS_R_NCACHENXRRSET:
+ qctx->redirected = true;
+ qctx->is_zone = false;
+ return (query_ncache(qctx, DNS_R_NCACHENXRRSET));
+ default:
+ break;
+ }
+
+ result = redirect2(qctx->client, qctx->fname, qctx->rdataset,
+ &qctx->node, &qctx->db, &qctx->version, qctx->type,
+ &qctx->is_zone);
+ switch (result) {
+ case ISC_R_SUCCESS:
+ inc_stats(qctx->client, ns_statscounter_nxdomainredirect);
+ return (query_prepresponse(qctx));
+ case DNS_R_CONTINUE:
+ inc_stats(qctx->client,
+ ns_statscounter_nxdomainredirect_rlookup);
+ SAVE(qctx->client->query.redirect.db, qctx->db);
+ SAVE(qctx->client->query.redirect.node, qctx->node);
+ SAVE(qctx->client->query.redirect.zone, qctx->zone);
+ qctx->client->query.redirect.qtype = qctx->qtype;
+ INSIST(qctx->rdataset != NULL);
+ SAVE(qctx->client->query.redirect.rdataset, qctx->rdataset);
+ SAVE(qctx->client->query.redirect.sigrdataset,
+ qctx->sigrdataset);
+ qctx->client->query.redirect.result = DNS_R_NCACHENXDOMAIN;
+ dns_name_copy(qctx->fname, qctx->client->query.redirect.fname);
+ qctx->client->query.redirect.authoritative =
+ qctx->authoritative;
+ qctx->client->query.redirect.is_zone = qctx->is_zone;
+ return (ns_query_done(qctx));
+ case DNS_R_NXRRSET:
+ qctx->redirected = true;
+ qctx->is_zone = true;
+ return (query_nodata(qctx, DNS_R_NXRRSET));
+ case DNS_R_NCACHENXRRSET:
+ qctx->redirected = true;
+ qctx->is_zone = false;
+ return (query_ncache(qctx, DNS_R_NCACHENXRRSET));
+ default:
+ break;
+ }
+
+ return (ISC_R_COMPLETE);
+}
+
+/*%
+ * Logging function to be passed to dns_nsec_noexistnodata.
+ */
+static void
+log_noexistnodata(void *val, int level, const char *fmt, ...) {
+ query_ctx_t *qctx = val;
+ va_list ap;
+
+ va_start(ap, fmt);
+ ns_client_logv(qctx->client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY,
+ level, fmt, ap);
+ va_end(ap);
+}
+
+static dns_ttl_t
+query_synthttl(dns_rdataset_t *soardataset, dns_rdataset_t *sigsoardataset,
+ dns_rdataset_t *p1rdataset, dns_rdataset_t *sigp1rdataset,
+ dns_rdataset_t *p2rdataset, dns_rdataset_t *sigp2rdataset) {
+ dns_rdata_soa_t soa;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_ttl_t ttl;
+ isc_result_t result;
+
+ REQUIRE(soardataset != NULL);
+ REQUIRE(sigsoardataset != NULL);
+ REQUIRE(p1rdataset != NULL);
+ REQUIRE(sigp1rdataset != NULL);
+
+ result = dns_rdataset_first(soardataset);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdataset_current(soardataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &soa, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+
+ ttl = ISC_MIN(soa.minimum, soardataset->ttl);
+ ttl = ISC_MIN(ttl, sigsoardataset->ttl);
+ ttl = ISC_MIN(ttl, p1rdataset->ttl);
+ ttl = ISC_MIN(ttl, sigp1rdataset->ttl);
+ if (p2rdataset != NULL) {
+ ttl = ISC_MIN(ttl, p2rdataset->ttl);
+ }
+ if (sigp2rdataset != NULL) {
+ ttl = ISC_MIN(ttl, sigp2rdataset->ttl);
+ }
+
+ return (ttl);
+}
+
+/*
+ * Synthesize a NODATA response from the SOA and covering NSEC in cache.
+ */
+static isc_result_t
+query_synthnodata(query_ctx_t *qctx, const dns_name_t *signer,
+ dns_rdataset_t **soardatasetp,
+ dns_rdataset_t **sigsoardatasetp) {
+ dns_name_t *name = NULL;
+ dns_ttl_t ttl;
+ isc_buffer_t *dbuf, b;
+ isc_result_t result;
+
+ /*
+ * Determine the correct TTL to use for the SOA and RRSIG
+ */
+ ttl = query_synthttl(*soardatasetp, *sigsoardatasetp, qctx->rdataset,
+ qctx->sigrdataset, NULL, NULL);
+ (*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl;
+
+ /*
+ * We want the SOA record to be first, so save the
+ * NODATA proof's name now or else discard it.
+ */
+ if (WANTDNSSEC(qctx->client)) {
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ } else {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ dbuf = ns_client_getnamebuf(qctx->client);
+ if (dbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ name = ns_client_newname(qctx->client, dbuf, &b);
+ if (name == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ dns_name_copy(signer, name);
+
+ /*
+ * Add SOA record. Omit the RRSIG if DNSSEC was not requested.
+ */
+ if (!WANTDNSSEC(qctx->client)) {
+ sigsoardatasetp = NULL;
+ }
+ query_addrrset(qctx, &name, soardatasetp, sigsoardatasetp, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ if (WANTDNSSEC(qctx->client)) {
+ /*
+ * Add NODATA proof.
+ */
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY);
+ }
+
+ result = ISC_R_SUCCESS;
+ inc_stats(qctx->client, ns_statscounter_nodatasynth);
+
+cleanup:
+ if (name != NULL) {
+ ns_client_releasename(qctx->client, &name);
+ }
+ return (result);
+}
+
+/*
+ * Synthesize a wildcard answer using the contents of 'rdataset'.
+ * qctx contains the NODATA proof.
+ */
+static isc_result_t
+query_synthwildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ dns_name_t *name = NULL;
+ isc_buffer_t *dbuf, b;
+ isc_result_t result;
+ dns_rdataset_t *cloneset = NULL, *clonesigset = NULL;
+ dns_rdataset_t **sigrdatasetp;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_synthwildcard");
+
+ /*
+ * We want the answer to be first, so save the
+ * NOQNAME proof's name now or else discard it.
+ */
+ if (WANTDNSSEC(qctx->client)) {
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ } else {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ dbuf = ns_client_getnamebuf(qctx->client);
+ if (dbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ name = ns_client_newname(qctx->client, dbuf, &b);
+ if (name == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ dns_name_copy(qctx->client->query.qname, name);
+
+ cloneset = ns_client_newrdataset(qctx->client);
+ if (cloneset == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ dns_rdataset_clone(rdataset, cloneset);
+
+ /*
+ * Add answer RRset. Omit the RRSIG if DNSSEC was not requested.
+ */
+ if (WANTDNSSEC(qctx->client)) {
+ clonesigset = ns_client_newrdataset(qctx->client);
+ if (clonesigset == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+ dns_rdataset_clone(sigrdataset, clonesigset);
+ sigrdatasetp = &clonesigset;
+ } else {
+ sigrdatasetp = NULL;
+ }
+
+ query_addrrset(qctx, &name, &cloneset, sigrdatasetp, dbuf,
+ DNS_SECTION_ANSWER);
+
+ if (WANTDNSSEC(qctx->client)) {
+ /*
+ * Add NOQNAME proof.
+ */
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY);
+ }
+
+ result = ISC_R_SUCCESS;
+ inc_stats(qctx->client, ns_statscounter_wildcardsynth);
+
+cleanup:
+ if (name != NULL) {
+ ns_client_releasename(qctx->client, &name);
+ }
+ if (cloneset != NULL) {
+ ns_client_putrdataset(qctx->client, &cloneset);
+ }
+ if (clonesigset != NULL) {
+ ns_client_putrdataset(qctx->client, &clonesigset);
+ }
+ return (result);
+}
+
+/*
+ * Add a synthesized CNAME record from the wildard RRset (rdataset)
+ * and NODATA proof by calling query_synthwildcard then setup to
+ * follow the CNAME.
+ */
+static isc_result_t
+query_synthcnamewildcard(query_ctx_t *qctx, dns_rdataset_t *rdataset,
+ dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+ dns_name_t *tname = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+
+ result = query_synthwildcard(qctx, rdataset, sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER;
+
+ /*
+ * Reset qname to be the target name of the CNAME and restart
+ * the query.
+ */
+ result = dns_message_gettempname(qctx->client->message, &tname);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = dns_rdataset_first(rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ return (result);
+ }
+
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ if (dns_name_equal(qctx->client->query.qname, &cname.cname)) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ dns_rdata_freestruct(&cname);
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_name_copy(&cname.cname, tname);
+
+ dns_rdata_freestruct(&cname);
+ ns_client_qnamereplace(qctx->client, tname);
+ qctx->want_restart = true;
+ if (!WANTRECURSION(qctx->client)) {
+ qctx->options |= DNS_GETDB_NOLOG;
+ }
+
+ return (result);
+}
+
+/*
+ * Synthesize a NXDOMAIN or NODATA response from qctx (which contains the
+ * NOQNAME proof), nowild + nowildrdataset + signowildrdataset (which
+ * contains the NOWILDCARD proof or NODATA at wildcard) and
+ * signer + soardatasetp + sigsoardatasetp which contain the
+ * SOA record + RRSIG for the negative answer.
+ */
+static isc_result_t
+query_synthnxdomainnodata(query_ctx_t *qctx, bool nodata, dns_name_t *nowild,
+ dns_rdataset_t *nowildrdataset,
+ dns_rdataset_t *signowildrdataset, dns_name_t *signer,
+ dns_rdataset_t **soardatasetp,
+ dns_rdataset_t **sigsoardatasetp) {
+ dns_name_t *name = NULL;
+ dns_ttl_t ttl;
+ isc_buffer_t *dbuf, b;
+ isc_result_t result;
+ dns_rdataset_t *cloneset = NULL, *clonesigset = NULL;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_synthnxdomain");
+
+ /*
+ * Determine the correct TTL to use for the SOA and RRSIG
+ */
+ ttl = query_synthttl(*soardatasetp, *sigsoardatasetp, qctx->rdataset,
+ qctx->sigrdataset, nowildrdataset,
+ signowildrdataset);
+ (*soardatasetp)->ttl = (*sigsoardatasetp)->ttl = ttl;
+
+ /*
+ * We want the SOA record to be first, so save the
+ * NOQNAME proof's name now or else discard it.
+ */
+ if (WANTDNSSEC(qctx->client)) {
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+ } else {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+
+ dbuf = ns_client_getnamebuf(qctx->client);
+ if (dbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ name = ns_client_newname(qctx->client, dbuf, &b);
+ if (name == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ dns_name_copy(signer, name);
+
+ /*
+ * Add SOA record. Omit the RRSIG if DNSSEC was not requested.
+ */
+ if (!WANTDNSSEC(qctx->client)) {
+ sigsoardatasetp = NULL;
+ }
+ query_addrrset(qctx, &name, soardatasetp, sigsoardatasetp, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ if (WANTDNSSEC(qctx->client)) {
+ /*
+ * Add NOQNAME proof.
+ */
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset,
+ &qctx->sigrdataset, NULL, DNS_SECTION_AUTHORITY);
+
+ dbuf = ns_client_getnamebuf(qctx->client);
+ if (dbuf == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ name = ns_client_newname(qctx->client, dbuf, &b);
+ if (name == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ dns_name_copy(nowild, name);
+
+ cloneset = ns_client_newrdataset(qctx->client);
+ clonesigset = ns_client_newrdataset(qctx->client);
+ if (cloneset == NULL || clonesigset == NULL) {
+ result = ISC_R_NOMEMORY;
+ goto cleanup;
+ }
+
+ dns_rdataset_clone(nowildrdataset, cloneset);
+ dns_rdataset_clone(signowildrdataset, clonesigset);
+
+ /*
+ * Add NOWILDCARD proof.
+ */
+ query_addrrset(qctx, &name, &cloneset, &clonesigset, dbuf,
+ DNS_SECTION_AUTHORITY);
+ }
+
+ if (nodata) {
+ inc_stats(qctx->client, ns_statscounter_nodatasynth);
+ } else {
+ qctx->client->message->rcode = dns_rcode_nxdomain;
+ inc_stats(qctx->client, ns_statscounter_nxdomainsynth);
+ }
+ result = ISC_R_SUCCESS;
+
+cleanup:
+ if (name != NULL) {
+ ns_client_releasename(qctx->client, &name);
+ }
+ if (cloneset != NULL) {
+ ns_client_putrdataset(qctx->client, &cloneset);
+ }
+ if (clonesigset != NULL) {
+ ns_client_putrdataset(qctx->client, &clonesigset);
+ }
+ return (result);
+}
+
+/*
+ * Check that all signer names in sigrdataset match the expected signer.
+ */
+static isc_result_t
+checksignames(dns_name_t *signer, dns_rdataset_t *sigrdataset) {
+ isc_result_t result;
+
+ 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 rrsig;
+
+ dns_rdataset_current(sigrdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &rrsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (dns_name_countlabels(signer) == 0) {
+ dns_name_copy(&rrsig.signer, signer);
+ } else if (!dns_name_equal(signer, &rrsig.signer)) {
+ return (ISC_R_FAILURE);
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Handle covering NSEC responses.
+ *
+ * Verify the NSEC record is appropriate for the QNAME; if not,
+ * redo the initial query without DNS_DBFIND_COVERINGNSEC.
+ *
+ * If the covering NSEC proves that the name exists but not the type,
+ * synthesize a NODATA response.
+ *
+ * If the name doesn't exist, compute the wildcard record and check whether
+ * the wildcard name exists or not. If we can't determine this, redo the
+ * initial query without DNS_DBFIND_COVERINGNSEC.
+ *
+ * If the wildcard name does not exist, compute the SOA name and look that
+ * up. If the SOA record does not exist, redo the initial query without
+ * DNS_DBFIND_COVERINGNSEC. If the SOA record exists, synthesize an
+ * NXDOMAIN response from the found records.
+ *
+ * If the wildcard name does exist, perform a lookup for the requested
+ * type at the wildcard name.
+ */
+static isc_result_t
+query_coveringnsec(query_ctx_t *qctx) {
+ dns_db_t *db = NULL;
+ dns_clientinfo_t ci;
+ dns_clientinfomethods_t cm;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t fixed;
+ dns_fixedname_t fnamespace;
+ dns_fixedname_t fnowild;
+ dns_fixedname_t fsigner;
+ dns_fixedname_t fwild;
+ dns_name_t *fname = NULL;
+ dns_name_t *namespace = NULL;
+ dns_name_t *nowild = NULL;
+ dns_name_t *signer = NULL;
+ dns_name_t *wild = NULL;
+ dns_name_t qname;
+ dns_rdataset_t *soardataset = NULL, *sigsoardataset = NULL;
+ dns_rdataset_t rdataset, sigrdataset;
+ bool done = false;
+ bool exists = true, data = true;
+ bool redirected = false;
+ isc_result_t result = ISC_R_SUCCESS;
+ unsigned int dboptions = qctx->client->query.dboptions;
+ unsigned int labels;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_coveringnsec");
+
+ dns_name_init(&qname, NULL);
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&sigrdataset);
+ namespace = dns_fixedname_initname(&fnamespace);
+
+ /*
+ * Check that the NSEC record is from the correct namespace.
+ * For records that belong to the parent zone (i.e. DS),
+ * remove a label to find the correct namespace.
+ */
+ dns_name_clone(qctx->client->query.qname, &qname);
+ labels = dns_name_countlabels(&qname);
+ if (dns_rdatatype_atparent(qctx->qtype) && labels > 1) {
+ dns_name_getlabelsequence(&qname, 1, labels - 1, &qname);
+ }
+ dns_view_sfd_find(qctx->view, &qname, namespace);
+ if (!dns_name_issubdomain(qctx->fname, namespace)) {
+ goto cleanup;
+ }
+
+ /*
+ * If we have no signer name, stop immediately.
+ */
+ if (!dns_rdataset_isassociated(qctx->sigrdataset)) {
+ goto cleanup;
+ }
+
+ wild = dns_fixedname_initname(&fwild);
+ fname = dns_fixedname_initname(&fixed);
+ signer = dns_fixedname_initname(&fsigner);
+ nowild = dns_fixedname_initname(&fnowild);
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, qctx->client, NULL);
+
+ /*
+ * All signer names must be the same to accept.
+ */
+ result = checksignames(signer, qctx->sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ /*
+ * If NSEC or RRSIG are missing from the type map
+ * reject the NSEC RRset.
+ */
+ if (!dns_nsec_requiredtypespresent(qctx->rdataset)) {
+ goto cleanup;
+ }
+
+ /*
+ * Check that we have the correct NOQNAME NSEC record.
+ */
+ result = dns_nsec_noexistnodata(qctx->qtype, qctx->client->query.qname,
+ qctx->fname, qctx->rdataset, &exists,
+ &data, wild, log_noexistnodata, qctx);
+
+ if (result != ISC_R_SUCCESS || (exists && data)) {
+ goto cleanup;
+ }
+
+ if (exists) {
+ if (qctx->type == dns_rdatatype_any) { /* XXX not yet */
+ goto cleanup;
+ }
+ if (!ISC_LIST_EMPTY(qctx->view->dns64) &&
+ (qctx->type == dns_rdatatype_a ||
+ qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
+ {
+ goto cleanup;
+ }
+ if (!qctx->resuming && !STALE(qctx->rdataset) &&
+ qctx->rdataset->ttl == 0 && RECURSIONOK(qctx->client))
+ {
+ goto cleanup;
+ }
+
+ soardataset = ns_client_newrdataset(qctx->client);
+ sigsoardataset = ns_client_newrdataset(qctx->client);
+ if (soardataset == NULL || sigsoardataset == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * Look for SOA record to construct NODATA response.
+ */
+ dns_db_attach(qctx->db, &db);
+ result = dns_db_findext(db, signer, qctx->version,
+ dns_rdatatype_soa, dboptions,
+ qctx->client->now, &node, fname, &cm,
+ &ci, soardataset, sigsoardataset);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ (void)query_synthnodata(qctx, signer, &soardataset,
+ &sigsoardataset);
+ done = true;
+ goto cleanup;
+ }
+
+ /*
+ * Look up the no-wildcard proof.
+ */
+ dns_db_attach(qctx->db, &db);
+ result = dns_db_findext(db, wild, qctx->version, qctx->type,
+ dboptions | DNS_DBFIND_COVERINGNSEC,
+ qctx->client->now, &node, nowild, &cm, &ci,
+ &rdataset, &sigrdataset);
+
+ if (rdataset.trust != dns_trust_secure ||
+ sigrdataset.trust != dns_trust_secure)
+ {
+ goto cleanup;
+ }
+
+ /*
+ * Zero TTL handling of wildcard record.
+ *
+ * We don't yet have code to handle synthesis and type ANY or dns64
+ * processing so we abort the synthesis here if there would be a
+ * interaction.
+ */
+ switch (result) {
+ case ISC_R_SUCCESS:
+ if (qctx->type == dns_rdatatype_any) { /* XXX not yet */
+ goto cleanup;
+ }
+ if (!ISC_LIST_EMPTY(qctx->view->dns64) &&
+ (qctx->type == dns_rdatatype_a ||
+ qctx->type == dns_rdatatype_aaaa)) /* XXX not yet */
+ {
+ goto cleanup;
+ }
+ FALLTHROUGH;
+ case DNS_R_CNAME:
+ if (!qctx->resuming && !STALE(&rdataset) && rdataset.ttl == 0 &&
+ RECURSIONOK(qctx->client))
+ {
+ goto cleanup;
+ }
+ default:
+ break;
+ }
+
+ switch (result) {
+ case DNS_R_COVERINGNSEC:
+ /*
+ * Check that the covering NSEC record is from the right
+ * namespace.
+ */
+ if (!dns_name_issubdomain(nowild, namespace)) {
+ goto cleanup;
+ }
+ result = dns_nsec_noexistnodata(qctx->qtype, wild, nowild,
+ &rdataset, &exists, &data, NULL,
+ log_noexistnodata, qctx);
+ if (result != ISC_R_SUCCESS || (exists && data)) {
+ goto cleanup;
+ }
+ break;
+ case ISC_R_SUCCESS: /* wild card match */
+ (void)query_synthwildcard(qctx, &rdataset, &sigrdataset);
+ done = true;
+ goto cleanup;
+ case DNS_R_CNAME: /* wild card cname */
+ (void)query_synthcnamewildcard(qctx, &rdataset, &sigrdataset);
+ done = true;
+ goto cleanup;
+ case DNS_R_NCACHENXRRSET: /* wild card nodata */
+ case DNS_R_NCACHENXDOMAIN: /* direct nxdomain */
+ default:
+ goto cleanup;
+ }
+
+ /*
+ * We now have the proof that we have an NXDOMAIN. Apply
+ * NXDOMAIN redirection if configured.
+ */
+ result = query_redirect(qctx);
+ if (result != ISC_R_COMPLETE) {
+ redirected = true;
+ goto cleanup;
+ }
+
+ /*
+ * Must be signed to accept.
+ */
+ if (!dns_rdataset_isassociated(&sigrdataset)) {
+ goto cleanup;
+ }
+
+ /*
+ * Check signer signer names again.
+ */
+ result = checksignames(signer, &sigrdataset);
+ if (result != ISC_R_SUCCESS) {
+ result = ISC_R_SUCCESS;
+ goto cleanup;
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+
+ soardataset = ns_client_newrdataset(qctx->client);
+ sigsoardataset = ns_client_newrdataset(qctx->client);
+ if (soardataset == NULL || sigsoardataset == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * Look for SOA record to construct NXDOMAIN response.
+ */
+ result = dns_db_findext(db, signer, qctx->version, dns_rdatatype_soa,
+ dboptions, qctx->client->now, &node, fname, &cm,
+ &ci, soardataset, sigsoardataset);
+
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+ (void)query_synthnxdomainnodata(qctx, exists, nowild, &rdataset,
+ &sigrdataset, signer, &soardataset,
+ &sigsoardataset);
+ done = true;
+
+cleanup:
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (dns_rdataset_isassociated(&sigrdataset)) {
+ dns_rdataset_disassociate(&sigrdataset);
+ }
+ if (soardataset != NULL) {
+ ns_client_putrdataset(qctx->client, &soardataset);
+ }
+ if (sigsoardataset != NULL) {
+ ns_client_putrdataset(qctx->client, &sigsoardataset);
+ }
+ if (db != NULL) {
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+ }
+
+ if (redirected) {
+ return (result);
+ }
+
+ if (!done) {
+ /*
+ * No covering NSEC was found; proceed with recursion.
+ */
+ qctx->findcoveringnsec = false;
+ if (qctx->fname != NULL) {
+ ns_client_releasename(qctx->client, &qctx->fname);
+ }
+ if (qctx->node != NULL) {
+ dns_db_detachnode(qctx->db, &qctx->node);
+ }
+ ns_client_putrdataset(qctx->client, &qctx->rdataset);
+ if (qctx->sigrdataset != NULL) {
+ ns_client_putrdataset(qctx->client, &qctx->sigrdataset);
+ }
+ return (query_lookup(qctx));
+ }
+
+ return (ns_query_done(qctx));
+}
+
+/*%
+ * Handle negative cache responses, DNS_R_NCACHENXRRSET or
+ * DNS_R_NCACHENXDOMAIN. (Note: may also be called with result
+ * set to DNS_R_NXDOMAIN when handling DNS64 lookups.)
+ */
+static isc_result_t
+query_ncache(query_ctx_t *qctx, isc_result_t result) {
+ INSIST(!qctx->is_zone);
+ INSIST(result == DNS_R_NCACHENXDOMAIN ||
+ result == DNS_R_NCACHENXRRSET || result == DNS_R_NXDOMAIN);
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_ncache");
+
+ CALL_HOOK(NS_QUERY_NCACHE_BEGIN, qctx);
+
+ qctx->authoritative = false;
+
+ if (result == DNS_R_NCACHENXDOMAIN) {
+ /*
+ * Set message rcode. (This is not done when
+ * result == DNS_R_NXDOMAIN because that means we're
+ * being called after a DNS64 lookup and don't want
+ * to update the rcode now.)
+ */
+ qctx->client->message->rcode = dns_rcode_nxdomain;
+
+ /* Look for RFC 1918 leakage from Internet. */
+ if (qctx->qtype == dns_rdatatype_ptr &&
+ qctx->client->message->rdclass == dns_rdataclass_in &&
+ dns_name_countlabels(qctx->fname) == 7)
+ {
+ warn_rfc1918(qctx->client, qctx->fname, qctx->rdataset);
+ }
+ }
+
+ return (query_nodata(qctx, result));
+
+cleanup:
+ return (result);
+}
+
+/*
+ * If we have a zero ttl from the cache, refetch.
+ */
+static isc_result_t
+query_zerottl_refetch(query_ctx_t *qctx) {
+ isc_result_t result;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_zerottl_refetch");
+
+ if (qctx->is_zone || qctx->resuming || STALE(qctx->rdataset) ||
+ qctx->rdataset->ttl != 0 || !RECURSIONOK(qctx->client))
+ {
+ return (ISC_R_COMPLETE);
+ }
+
+ qctx_clean(qctx);
+
+ INSIST(!REDIRECT(qctx->client));
+
+ result = ns_query_recurse(qctx->client, qctx->qtype,
+ qctx->client->query.qname, NULL, NULL,
+ qctx->resuming);
+ if (result == ISC_R_SUCCESS) {
+ CALL_HOOK(NS_QUERY_ZEROTTL_RECURSE, qctx);
+ qctx->client->query.attributes |= NS_QUERYATTR_RECURSING;
+
+ if (qctx->dns64) {
+ qctx->client->query.attributes |= NS_QUERYATTR_DNS64;
+ }
+ if (qctx->dns64_exclude) {
+ qctx->client->query.attributes |=
+ NS_QUERYATTR_DNS64EXCLUDE;
+ }
+ } else {
+ /*
+ * There was a zero ttl from the cache, don't fallback to
+ * serve-stale lookup.
+ */
+ QUERY_ERROR(qctx, result);
+ }
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Handle CNAME responses.
+ */
+static isc_result_t
+query_cname(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ dns_name_t *tname = NULL;
+ dns_rdataset_t *trdataset = NULL;
+ dns_rdataset_t **sigrdatasetp = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_cname_t cname;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_cname");
+
+ CALL_HOOK(NS_QUERY_CNAME_BEGIN, qctx);
+
+ result = query_zerottl_refetch(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+
+ /*
+ * Keep a copy of the rdataset. We have to do this because
+ * query_addrrset may clear 'rdataset' (to prevent the
+ * cleanup code from cleaning it up).
+ */
+ trdataset = qctx->rdataset;
+
+ /*
+ * Add the CNAME to the answer section.
+ */
+ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) {
+ sigrdatasetp = &qctx->sigrdataset;
+ }
+
+ if (WANTDNSSEC(qctx->client) &&
+ (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0)
+ {
+ dns_fixedname_init(&qctx->wildcardname);
+ dns_name_copy(qctx->fname,
+ dns_fixedname_name(&qctx->wildcardname));
+ qctx->need_wildcardproof = true;
+ }
+
+ if (NOQNAME(qctx->rdataset) && WANTDNSSEC(qctx->client)) {
+ qctx->noqname = qctx->rdataset;
+ } else {
+ qctx->noqname = NULL;
+ }
+
+ if (!qctx->is_zone && RECURSIONOK(qctx->client)) {
+ query_prefetch(qctx->client, qctx->fname, qctx->rdataset);
+ }
+
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp,
+ qctx->dbuf, DNS_SECTION_ANSWER);
+
+ query_addnoqnameproof(qctx);
+
+ /*
+ * We set the PARTIALANSWER attribute so that if anything goes
+ * wrong later on, we'll return what we've got so far.
+ */
+ qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER;
+
+ /*
+ * Reset qname to be the target name of the CNAME and restart
+ * the query.
+ */
+ result = dns_message_gettempname(qctx->client->message, &tname);
+ if (result != ISC_R_SUCCESS) {
+ return (ns_query_done(qctx));
+ }
+
+ result = dns_rdataset_first(trdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ return (ns_query_done(qctx));
+ }
+
+ dns_rdataset_current(trdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &cname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ dns_name_copy(&cname.cname, tname);
+
+ dns_rdata_freestruct(&cname);
+ ns_client_qnamereplace(qctx->client, tname);
+ qctx->want_restart = true;
+ if (!WANTRECURSION(qctx->client)) {
+ qctx->options |= DNS_GETDB_NOLOG;
+ }
+
+ query_addauth(qctx);
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*
+ * Handle DNAME responses.
+ */
+static isc_result_t
+query_dname(query_ctx_t *qctx) {
+ dns_name_t *tname, *prefix;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_dname_t dname;
+ dns_fixedname_t fixed;
+ dns_rdataset_t *trdataset;
+ dns_rdataset_t **sigrdatasetp = NULL;
+ dns_namereln_t namereln;
+ isc_buffer_t b;
+ int order;
+ isc_result_t result = ISC_R_UNSET;
+ unsigned int nlabels;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_dname");
+
+ CALL_HOOK(NS_QUERY_DNAME_BEGIN, qctx);
+
+ /*
+ * Compare the current qname to the found name. We need
+ * to know how many labels and bits are in common because
+ * we're going to have to split qname later on.
+ */
+ namereln = dns_name_fullcompare(qctx->client->query.qname, qctx->fname,
+ &order, &nlabels);
+ INSIST(namereln == dns_namereln_subdomain);
+
+ /*
+ * Keep a copy of the rdataset. We have to do this because
+ * query_addrrset may clear 'rdataset' (to prevent the
+ * cleanup code from cleaning it up).
+ */
+ trdataset = qctx->rdataset;
+
+ /*
+ * Add the DNAME to the answer section.
+ */
+ if (WANTDNSSEC(qctx->client) && qctx->sigrdataset != NULL) {
+ sigrdatasetp = &qctx->sigrdataset;
+ }
+
+ if (WANTDNSSEC(qctx->client) &&
+ (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0)
+ {
+ dns_fixedname_init(&qctx->wildcardname);
+ dns_name_copy(qctx->fname,
+ dns_fixedname_name(&qctx->wildcardname));
+ qctx->need_wildcardproof = true;
+ }
+
+ if (!qctx->is_zone && RECURSIONOK(qctx->client)) {
+ query_prefetch(qctx->client, qctx->fname, qctx->rdataset);
+ }
+ query_addrrset(qctx, &qctx->fname, &qctx->rdataset, sigrdatasetp,
+ qctx->dbuf, DNS_SECTION_ANSWER);
+
+ /*
+ * We set the PARTIALANSWER attribute so that if anything goes
+ * wrong later on, we'll return what we've got so far.
+ */
+ qctx->client->query.attributes |= NS_QUERYATTR_PARTIALANSWER;
+
+ /*
+ * Get the target name of the DNAME.
+ */
+ tname = NULL;
+ result = dns_message_gettempname(qctx->client->message, &tname);
+ if (result != ISC_R_SUCCESS) {
+ return (ns_query_done(qctx));
+ }
+
+ result = dns_rdataset_first(trdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ return (ns_query_done(qctx));
+ }
+
+ dns_rdataset_current(trdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &dname, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ dns_rdata_reset(&rdata);
+
+ dns_name_copy(&dname.dname, tname);
+ dns_rdata_freestruct(&dname);
+
+ /*
+ * Construct the new qname consisting of
+ * <found name prefix>.<dname target>
+ */
+ prefix = dns_fixedname_initname(&fixed);
+ dns_name_split(qctx->client->query.qname, nlabels, prefix, NULL);
+ INSIST(qctx->fname == NULL);
+ qctx->dbuf = ns_client_getnamebuf(qctx->client);
+ if (qctx->dbuf == NULL) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ return (ns_query_done(qctx));
+ }
+ qctx->fname = ns_client_newname(qctx->client, qctx->dbuf, &b);
+ if (qctx->fname == NULL) {
+ dns_message_puttempname(qctx->client->message, &tname);
+ return (ns_query_done(qctx));
+ }
+ result = dns_name_concatenate(prefix, tname, qctx->fname, NULL);
+ dns_message_puttempname(qctx->client->message, &tname);
+
+ /*
+ * RFC2672, section 4.1, subsection 3c says
+ * we should return YXDOMAIN if the constructed
+ * name would be too long.
+ */
+ if (result == DNS_R_NAMETOOLONG) {
+ qctx->client->message->rcode = dns_rcode_yxdomain;
+ }
+ if (result != ISC_R_SUCCESS) {
+ return (ns_query_done(qctx));
+ }
+
+ ns_client_keepname(qctx->client, qctx->fname, qctx->dbuf);
+
+ /*
+ * Synthesize a CNAME consisting of
+ * <old qname> <dname ttl> CNAME <new qname>
+ * with <dname trust value>
+ *
+ * Synthesize a CNAME so old old clients that don't understand
+ * DNAME can chain.
+ *
+ * We do not try to synthesize a signature because we hope
+ * that security aware servers will understand DNAME. Also,
+ * even if we had an online key, making a signature
+ * on-the-fly is costly, and not really legitimate anyway
+ * since the synthesized CNAME is NOT in the zone.
+ */
+ result = query_addcname(qctx, trdataset->trust, trdataset->ttl);
+ if (result != ISC_R_SUCCESS) {
+ return (ns_query_done(qctx));
+ }
+
+ /*
+ * If the original query was not for a CNAME or ANY then follow the
+ * CNAME.
+ */
+ if (qctx->qtype != dns_rdatatype_cname &&
+ qctx->qtype != dns_rdatatype_any)
+ {
+ /*
+ * Switch to the new qname and restart.
+ */
+ ns_client_qnamereplace(qctx->client, qctx->fname);
+ qctx->fname = NULL;
+ qctx->want_restart = true;
+ if (!WANTRECURSION(qctx->client)) {
+ qctx->options |= DNS_GETDB_NOLOG;
+ }
+ }
+
+ query_addauth(qctx);
+
+ return (ns_query_done(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Add CNAME to response.
+ */
+static isc_result_t
+query_addcname(query_ctx_t *qctx, dns_trust_t trust, dns_ttl_t ttl) {
+ ns_client_t *client = qctx->client;
+ dns_rdataset_t *rdataset = NULL;
+ dns_rdatalist_t *rdatalist = NULL;
+ dns_rdata_t *rdata = NULL;
+ isc_region_t r;
+ dns_name_t *aname = NULL;
+ isc_result_t result;
+
+ result = dns_message_gettempname(client->message, &aname);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ dns_name_copy(client->query.qname, aname);
+
+ result = dns_message_gettemprdatalist(client->message, &rdatalist);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(client->message, &aname);
+ return (result);
+ }
+
+ result = dns_message_gettemprdata(client->message, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(client->message, &aname);
+ dns_message_puttemprdatalist(client->message, &rdatalist);
+ return (result);
+ }
+
+ result = dns_message_gettemprdataset(client->message, &rdataset);
+ if (result != ISC_R_SUCCESS) {
+ dns_message_puttempname(client->message, &aname);
+ dns_message_puttemprdatalist(client->message, &rdatalist);
+ dns_message_puttemprdata(client->message, &rdata);
+ return (result);
+ }
+
+ rdatalist->type = dns_rdatatype_cname;
+ rdatalist->rdclass = client->message->rdclass;
+ rdatalist->ttl = ttl;
+
+ dns_name_toregion(qctx->fname, &r);
+ rdata->data = r.base;
+ rdata->length = r.length;
+ rdata->rdclass = client->message->rdclass;
+ rdata->type = dns_rdatatype_cname;
+
+ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) ==
+ ISC_R_SUCCESS);
+ rdataset->trust = trust;
+ dns_rdataset_setownercase(rdataset, aname);
+
+ query_addrrset(qctx, &aname, &rdataset, NULL, NULL, DNS_SECTION_ANSWER);
+ if (rdataset != NULL) {
+ if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+ dns_message_puttemprdataset(client->message, &rdataset);
+ }
+ if (aname != NULL) {
+ dns_message_puttempname(client->message, &aname);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Prepare to respond: determine whether a wildcard proof is needed,
+ * then hand off to query_respond() or (for type ANY queries)
+ * query_respond_any().
+ */
+static isc_result_t
+query_prepresponse(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "query_prepresponse");
+
+ CALL_HOOK(NS_QUERY_PREP_RESPONSE_BEGIN, qctx);
+
+ if (WANTDNSSEC(qctx->client) &&
+ (qctx->fname->attributes & DNS_NAMEATTR_WILDCARD) != 0)
+ {
+ dns_fixedname_init(&qctx->wildcardname);
+ dns_name_copy(qctx->fname,
+ dns_fixedname_name(&qctx->wildcardname));
+ qctx->need_wildcardproof = true;
+ }
+
+ if (qctx->type == dns_rdatatype_any) {
+ return (query_respond_any(qctx));
+ }
+
+ result = query_zerottl_refetch(qctx);
+ if (result != ISC_R_COMPLETE) {
+ return (result);
+ }
+
+ return (query_respond(qctx));
+
+cleanup:
+ return (result);
+}
+
+/*%
+ * Add SOA to the authority section when sending negative responses
+ * (or to the additional section if sending negative responses triggered
+ * by RPZ rewriting.)
+ */
+static isc_result_t
+query_addsoa(query_ctx_t *qctx, unsigned int override_ttl,
+ dns_section_t section) {
+ ns_client_t *client = qctx->client;
+ dns_name_t *name = NULL;
+ dns_dbnode_t *node = NULL;
+ isc_result_t result, eresult = ISC_R_SUCCESS;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t **sigrdatasetp = NULL;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addsoa");
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Don't add the SOA record for test which set "-T nosoa".
+ */
+ if (((client->sctx->options & NS_SERVER_NOSOA) != 0) &&
+ (!WANTDNSSEC(client) || !dns_rdataset_isassociated(qctx->rdataset)))
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * Get resources and make 'name' be the database origin.
+ */
+ result = dns_message_gettempname(client->message, &name);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ /*
+ * We'll be releasing 'name' before returning, so it's safe to
+ * use clone instead of copying here.
+ */
+ dns_name_clone(dns_db_origin(qctx->db), name);
+
+ rdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL) {
+ CTRACE(ISC_LOG_ERROR, "unable to allocate rdataset");
+ eresult = DNS_R_SERVFAIL;
+ goto cleanup;
+ }
+ if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ CTRACE(ISC_LOG_ERROR, "unable to allocate sigrdataset");
+ eresult = DNS_R_SERVFAIL;
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Find the SOA.
+ */
+ result = dns_db_getoriginnode(qctx->db, &node);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_findrdataset(qctx->db, node, qctx->version,
+ dns_rdatatype_soa, 0, client->now,
+ rdataset, sigrdataset);
+ } else {
+ dns_fixedname_t foundname;
+ dns_name_t *fname;
+
+ fname = dns_fixedname_initname(&foundname);
+
+ result = dns_db_findext(qctx->db, name, qctx->version,
+ dns_rdatatype_soa,
+ client->query.dboptions, 0, &node,
+ fname, &cm, &ci, rdataset, sigrdataset);
+ }
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * This is bad. We tried to get the SOA RR at the zone top
+ * and it didn't work!
+ */
+ CTRACE(ISC_LOG_ERROR, "unable to find SOA RR at zone apex");
+ eresult = DNS_R_SERVFAIL;
+ } else {
+ /*
+ * Extract the SOA MINIMUM.
+ */
+ dns_rdata_soa_t soa;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ 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 (override_ttl != UINT32_MAX && override_ttl < rdataset->ttl)
+ {
+ rdataset->ttl = override_ttl;
+ if (sigrdataset != NULL) {
+ sigrdataset->ttl = override_ttl;
+ }
+ }
+
+ /*
+ * Add the SOA and its SIG to the response, with the
+ * TTLs adjusted per RFC2308 section 3.
+ */
+ if (rdataset->ttl > soa.minimum) {
+ rdataset->ttl = soa.minimum;
+ }
+ if (sigrdataset != NULL && sigrdataset->ttl > soa.minimum) {
+ sigrdataset->ttl = soa.minimum;
+ }
+
+ if (sigrdataset != NULL) {
+ sigrdatasetp = &sigrdataset;
+ } else {
+ sigrdatasetp = NULL;
+ }
+
+ if (section == DNS_SECTION_ADDITIONAL) {
+ rdataset->attributes |= DNS_RDATASETATTR_REQUIRED;
+ }
+ query_addrrset(qctx, &name, &rdataset, sigrdatasetp, NULL,
+ section);
+ }
+
+cleanup:
+ ns_client_putrdataset(client, &rdataset);
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (name != NULL) {
+ ns_client_releasename(client, &name);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(qctx->db, &node);
+ }
+
+ return (eresult);
+}
+
+/*%
+ * Add NS to authority section (used when the zone apex is already known).
+ */
+static isc_result_t
+query_addns(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ isc_result_t result, eresult;
+ dns_name_t *name = NULL, *fname;
+ dns_dbnode_t *node = NULL;
+ dns_fixedname_t foundname;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t **sigrdatasetp = NULL;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns");
+
+ /*
+ * Initialization.
+ */
+ eresult = ISC_R_SUCCESS;
+ fname = dns_fixedname_initname(&foundname);
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Get resources and make 'name' be the database origin.
+ */
+ result = dns_message_gettempname(client->message, &name);
+ if (result != ISC_R_SUCCESS) {
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_message_gettempname "
+ "failed: done");
+ return (result);
+ }
+ dns_name_clone(dns_db_origin(qctx->db), name);
+ rdataset = ns_client_newrdataset(client);
+ if (rdataset == NULL) {
+ CTRACE(ISC_LOG_ERROR, "query_addns: ns_client_newrdataset "
+ "failed");
+ eresult = DNS_R_SERVFAIL;
+ goto cleanup;
+ }
+
+ if (WANTDNSSEC(client) && dns_db_issecure(qctx->db)) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ CTRACE(ISC_LOG_ERROR, "query_addns: "
+ "ns_client_newrdataset failed");
+ eresult = DNS_R_SERVFAIL;
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Find the NS rdataset.
+ */
+ result = dns_db_getoriginnode(qctx->db, &node);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_findrdataset(qctx->db, node, qctx->version,
+ dns_rdatatype_ns, 0, client->now,
+ rdataset, sigrdataset);
+ } else {
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns: calling dns_db_find");
+ result = dns_db_findext(qctx->db, name, NULL, dns_rdatatype_ns,
+ client->query.dboptions, 0, &node,
+ fname, &cm, &ci, rdataset, sigrdataset);
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns: dns_db_find complete");
+ }
+ if (result != ISC_R_SUCCESS) {
+ CTRACE(ISC_LOG_ERROR, "query_addns: "
+ "dns_db_findrdataset or dns_db_find "
+ "failed");
+ /*
+ * This is bad. We tried to get the NS rdataset at the zone
+ * top and it didn't work!
+ */
+ eresult = DNS_R_SERVFAIL;
+ } else {
+ if (sigrdataset != NULL) {
+ sigrdatasetp = &sigrdataset;
+ }
+ query_addrrset(qctx, &name, &rdataset, sigrdatasetp, NULL,
+ DNS_SECTION_AUTHORITY);
+ }
+
+cleanup:
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns: cleanup");
+ ns_client_putrdataset(client, &rdataset);
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (name != NULL) {
+ ns_client_releasename(client, &name);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(qctx->db, &node);
+ }
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addns: done");
+ return (eresult);
+}
+
+/*%
+ * Find the zone cut and add the best NS rrset to the authority section.
+ */
+static void
+query_addbestns(query_ctx_t *qctx) {
+ ns_client_t *client = qctx->client;
+ dns_db_t *db = NULL, *zdb = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_name_t *fname = NULL, *zfname = NULL;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_rdataset_t *zrdataset = NULL, *zsigrdataset = NULL;
+ bool is_zone = false, use_zone = false;
+ isc_buffer_t *dbuf = NULL;
+ isc_result_t result;
+ dns_dbversion_t *version = NULL;
+ dns_zone_t *zone = NULL;
+ isc_buffer_t b;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addbestns");
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * Find the right database.
+ */
+ result = query_getdb(client, client->query.qname, dns_rdatatype_ns, 0,
+ &zone, &db, &version, &is_zone);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+db_find:
+ /*
+ * We'll need some resources...
+ */
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ rdataset = ns_client_newrdataset(client);
+ if (fname == NULL || rdataset == NULL) {
+ goto cleanup;
+ }
+
+ /*
+ * Get the RRSIGs if the client requested them or if we may
+ * need to validate answers from the cache.
+ */
+ if (WANTDNSSEC(client) || !is_zone) {
+ sigrdataset = ns_client_newrdataset(client);
+ if (sigrdataset == NULL) {
+ goto cleanup;
+ }
+ }
+
+ /*
+ * Now look for the zonecut.
+ */
+ if (is_zone) {
+ result = dns_db_findext(
+ db, client->query.qname, version, dns_rdatatype_ns,
+ client->query.dboptions, client->now, &node, fname, &cm,
+ &ci, rdataset, sigrdataset);
+ if (result != DNS_R_DELEGATION) {
+ goto cleanup;
+ }
+ if (USECACHE(client)) {
+ ns_client_keepname(client, fname, dbuf);
+ dns_db_detachnode(db, &node);
+ SAVE(zdb, db);
+ SAVE(zfname, fname);
+ SAVE(zrdataset, rdataset);
+ SAVE(zsigrdataset, sigrdataset);
+ version = NULL;
+ dns_db_attach(client->view->cachedb, &db);
+ is_zone = false;
+ goto db_find;
+ }
+ } else {
+ result = dns_db_findzonecut(
+ db, client->query.qname, client->query.dboptions,
+ client->now, &node, fname, NULL, rdataset, sigrdataset);
+ if (result == ISC_R_SUCCESS) {
+ if (zfname != NULL &&
+ !dns_name_issubdomain(fname, zfname))
+ {
+ /*
+ * We found a zonecut in the cache, but our
+ * zone delegation is better.
+ */
+ use_zone = true;
+ }
+ } else if (result == ISC_R_NOTFOUND && zfname != NULL) {
+ /*
+ * We didn't find anything in the cache, but we
+ * have a zone delegation, so use it.
+ */
+ use_zone = true;
+ } else {
+ goto cleanup;
+ }
+ }
+
+ if (use_zone) {
+ ns_client_releasename(client, &fname);
+ /*
+ * We've already done ns_client_keepname() on
+ * zfname, so we must set dbuf to NULL to
+ * prevent query_addrrset() from trying to
+ * call ns_client_keepname() again.
+ */
+ dbuf = NULL;
+ ns_client_putrdataset(client, &rdataset);
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ dns_db_detach(&db);
+
+ RESTORE(db, zdb);
+ RESTORE(fname, zfname);
+ RESTORE(rdataset, zrdataset);
+ RESTORE(sigrdataset, zsigrdataset);
+ }
+
+ /*
+ * Attempt to validate RRsets that are pending or that are glue.
+ */
+ if ((DNS_TRUST_PENDING(rdataset->trust) ||
+ (sigrdataset != NULL && DNS_TRUST_PENDING(sigrdataset->trust))) &&
+ !validate(client, db, fname, rdataset, sigrdataset) &&
+ !PENDINGOK(client->query.dboptions))
+ {
+ goto cleanup;
+ }
+
+ if ((DNS_TRUST_GLUE(rdataset->trust) ||
+ (sigrdataset != NULL && DNS_TRUST_GLUE(sigrdataset->trust))) &&
+ !validate(client, db, fname, rdataset, sigrdataset) &&
+ SECURE(client) && WANTDNSSEC(client))
+ {
+ goto cleanup;
+ }
+
+ /*
+ * If the answer is secure only add NS records if they are secure
+ * when the client may be looking for AD in the response.
+ */
+ if (SECURE(client) && (WANTDNSSEC(client) || WANTAD(client)) &&
+ ((rdataset->trust != dns_trust_secure) ||
+ (sigrdataset != NULL && sigrdataset->trust != dns_trust_secure)))
+ {
+ goto cleanup;
+ }
+
+ /*
+ * If the client doesn't want DNSSEC we can discard the sigrdataset
+ * now.
+ */
+ if (!WANTDNSSEC(client)) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+cleanup:
+ if (rdataset != NULL) {
+ ns_client_putrdataset(client, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ if (zdb != NULL) {
+ ns_client_putrdataset(client, &zrdataset);
+ if (zsigrdataset != NULL) {
+ ns_client_putrdataset(client, &zsigrdataset);
+ }
+ if (zfname != NULL) {
+ ns_client_releasename(client, &zfname);
+ }
+ dns_db_detach(&zdb);
+ }
+}
+
+static void
+query_addwildcardproof(query_ctx_t *qctx, bool ispositive, bool nodata) {
+ ns_client_t *client = qctx->client;
+ isc_buffer_t *dbuf, b;
+ dns_name_t *name;
+ dns_name_t *fname = NULL;
+ dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
+ dns_fixedname_t wfixed;
+ dns_name_t *wname;
+ dns_dbnode_t *node = NULL;
+ unsigned int options;
+ unsigned int olabels, nlabels, labels;
+ isc_result_t result;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_rdata_nsec_t nsec;
+ bool have_wname;
+ int order;
+ dns_fixedname_t cfixed;
+ dns_name_t *cname;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+
+ CTRACE(ISC_LOG_DEBUG(3), "query_addwildcardproof");
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+ dns_clientinfo_init(&ci, client, NULL);
+
+ /*
+ * If a name has been specifically flagged as needing
+ * a wildcard proof then it will have been copied to
+ * qctx->wildcardname. Otherwise we just use the client
+ * QNAME.
+ */
+ if (qctx->need_wildcardproof) {
+ name = dns_fixedname_name(&qctx->wildcardname);
+ } else {
+ name = client->query.qname;
+ }
+
+ /*
+ * Get the NOQNAME proof then if !ispositive
+ * get the NOWILDCARD proof.
+ *
+ * DNS_DBFIND_NOWILD finds the NSEC records that covers the
+ * name ignoring any wildcard. From the owner and next names
+ * of this record you can compute which wildcard (if it exists)
+ * will match by finding the longest common suffix of the
+ * owner name and next names with the qname and prefixing that
+ * with the wildcard label.
+ *
+ * e.g.
+ * Given:
+ * example SOA
+ * example NSEC b.example
+ * b.example A
+ * b.example NSEC a.d.example
+ * a.d.example A
+ * a.d.example NSEC g.f.example
+ * g.f.example A
+ * g.f.example NSEC z.i.example
+ * z.i.example A
+ * z.i.example NSEC example
+ *
+ * QNAME:
+ * a.example -> example NSEC b.example
+ * owner common example
+ * next common example
+ * wild *.example
+ * d.b.example -> b.example NSEC a.d.example
+ * owner common b.example
+ * next common example
+ * wild *.b.example
+ * a.f.example -> a.d.example NSEC g.f.example
+ * owner common example
+ * next common f.example
+ * wild *.f.example
+ * j.example -> z.i.example NSEC example
+ * owner common example
+ * next common example
+ * wild *.example
+ */
+ options = client->query.dboptions | DNS_DBFIND_NOWILD;
+ wname = dns_fixedname_initname(&wfixed);
+again:
+ have_wname = false;
+ /*
+ * We'll need some resources...
+ */
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ rdataset = ns_client_newrdataset(client);
+ sigrdataset = ns_client_newrdataset(client);
+ if (fname == NULL || rdataset == NULL || sigrdataset == NULL) {
+ goto cleanup;
+ }
+
+ result = dns_db_findext(qctx->db, name, qctx->version,
+ dns_rdatatype_nsec, options, 0, &node, fname,
+ &cm, &ci, rdataset, sigrdataset);
+ if (node != NULL) {
+ dns_db_detachnode(qctx->db, &node);
+ }
+
+ if (!dns_rdataset_isassociated(rdataset)) {
+ /*
+ * No NSEC proof available, return NSEC3 proofs instead.
+ */
+ cname = dns_fixedname_initname(&cfixed);
+ /*
+ * Find the closest encloser.
+ */
+ dns_name_copy(name, cname);
+ while (result == DNS_R_NXDOMAIN) {
+ labels = dns_name_countlabels(cname) - 1;
+ /*
+ * Sanity check.
+ */
+ if (labels == 0U) {
+ goto cleanup;
+ }
+ dns_name_split(cname, labels, NULL, cname);
+ result = dns_db_findext(qctx->db, cname, qctx->version,
+ dns_rdatatype_nsec, options, 0,
+ NULL, fname, &cm, &ci, NULL,
+ NULL);
+ }
+ /*
+ * Add closest (provable) encloser NSEC3.
+ */
+ query_findclosestnsec3(cname, qctx->db, qctx->version, client,
+ rdataset, sigrdataset, fname, true,
+ cname);
+ if (!dns_rdataset_isassociated(rdataset)) {
+ goto cleanup;
+ }
+ if (!ispositive) {
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset,
+ dbuf, DNS_SECTION_AUTHORITY);
+ }
+
+ /*
+ * Replace resources which were consumed by query_addrrset.
+ */
+ if (fname == NULL) {
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ }
+
+ if (rdataset == NULL) {
+ rdataset = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ if (sigrdataset == NULL) {
+ sigrdataset = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(sigrdataset)) {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+
+ if (fname == NULL || rdataset == NULL || sigrdataset == NULL) {
+ goto cleanup;
+ }
+ /*
+ * Add no qname proof.
+ */
+ labels = dns_name_countlabels(cname) + 1;
+ if (dns_name_countlabels(name) == labels) {
+ dns_name_copy(name, wname);
+ } else {
+ dns_name_split(name, labels, NULL, wname);
+ }
+
+ query_findclosestnsec3(wname, qctx->db, qctx->version, client,
+ rdataset, sigrdataset, fname, false,
+ NULL);
+ if (!dns_rdataset_isassociated(rdataset)) {
+ goto cleanup;
+ }
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ if (ispositive) {
+ goto cleanup;
+ }
+
+ /*
+ * Replace resources which were consumed by query_addrrset.
+ */
+ if (fname == NULL) {
+ dbuf = ns_client_getnamebuf(client);
+ if (dbuf == NULL) {
+ goto cleanup;
+ }
+ fname = ns_client_newname(client, dbuf, &b);
+ }
+
+ if (rdataset == NULL) {
+ rdataset = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(rdataset)) {
+ dns_rdataset_disassociate(rdataset);
+ }
+
+ if (sigrdataset == NULL) {
+ sigrdataset = ns_client_newrdataset(client);
+ } else if (dns_rdataset_isassociated(sigrdataset)) {
+ dns_rdataset_disassociate(sigrdataset);
+ }
+
+ if (fname == NULL || rdataset == NULL || sigrdataset == NULL) {
+ goto cleanup;
+ }
+ /*
+ * Add the no wildcard proof.
+ */
+ result = dns_name_concatenate(dns_wildcardname, cname, wname,
+ NULL);
+ if (result != ISC_R_SUCCESS) {
+ goto cleanup;
+ }
+
+ query_findclosestnsec3(wname, qctx->db, qctx->version, client,
+ rdataset, sigrdataset, fname, nodata,
+ NULL);
+ if (!dns_rdataset_isassociated(rdataset)) {
+ goto cleanup;
+ }
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+
+ goto cleanup;
+ } else if (result == DNS_R_NXDOMAIN) {
+ if (!ispositive) {
+ result = dns_rdataset_first(rdataset);
+ }
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_current(rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &nsec, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ (void)dns_name_fullcompare(name, fname, &order,
+ &olabels);
+ (void)dns_name_fullcompare(name, &nsec.next, &order,
+ &nlabels);
+ /*
+ * Check for a pathological condition created when
+ * serving some malformed signed zones and bail out.
+ */
+ if (dns_name_countlabels(name) == nlabels) {
+ goto cleanup;
+ }
+
+ if (olabels > nlabels) {
+ dns_name_split(name, olabels, NULL, wname);
+ } else {
+ dns_name_split(name, nlabels, NULL, wname);
+ }
+ result = dns_name_concatenate(dns_wildcardname, wname,
+ wname, NULL);
+ if (result == ISC_R_SUCCESS) {
+ have_wname = true;
+ }
+ dns_rdata_freestruct(&nsec);
+ }
+ query_addrrset(qctx, &fname, &rdataset, &sigrdataset, dbuf,
+ DNS_SECTION_AUTHORITY);
+ }
+ if (rdataset != NULL) {
+ ns_client_putrdataset(client, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+ if (have_wname) {
+ ispositive = true; /* prevent loop */
+ if (!dns_name_equal(name, wname)) {
+ name = wname;
+ goto again;
+ }
+ }
+cleanup:
+ if (rdataset != NULL) {
+ ns_client_putrdataset(client, &rdataset);
+ }
+ if (sigrdataset != NULL) {
+ ns_client_putrdataset(client, &sigrdataset);
+ }
+ if (fname != NULL) {
+ ns_client_releasename(client, &fname);
+ }
+}
+
+/*%
+ * Add NS records, and NSEC/NSEC3 wildcard proof records if needed,
+ * to the authority section.
+ */
+static void
+query_addauth(query_ctx_t *qctx) {
+ CCTRACE(ISC_LOG_DEBUG(3), "query_addauth");
+ /*
+ * Add NS records to the authority section (if we haven't already
+ * added them to the answer section).
+ */
+ if (!qctx->want_restart && !NOAUTHORITY(qctx->client)) {
+ if (qctx->is_zone) {
+ if (!qctx->answer_has_ns) {
+ (void)query_addns(qctx);
+ }
+ } else if (!qctx->answer_has_ns &&
+ qctx->qtype != dns_rdatatype_ns)
+ {
+ if (qctx->fname != NULL) {
+ ns_client_releasename(qctx->client,
+ &qctx->fname);
+ }
+ query_addbestns(qctx);
+ }
+ }
+
+ /*
+ * Add NSEC records to the authority section if they're needed for
+ * DNSSEC wildcard proofs.
+ */
+ if (qctx->need_wildcardproof && dns_db_issecure(qctx->db)) {
+ query_addwildcardproof(qctx, true, false);
+ }
+}
+
+/*
+ * Find the sort order of 'rdata' in the topology-like
+ * ACL forming the second element in a 2-element top-level
+ * sortlist statement.
+ */
+static int
+query_sortlist_order_2element(const dns_rdata_t *rdata, const void *arg) {
+ isc_netaddr_t netaddr;
+
+ if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) {
+ return (INT_MAX);
+ }
+ return (ns_sortlist_addrorder2(&netaddr, arg));
+}
+
+/*
+ * Find the sort order of 'rdata' in the matching element
+ * of a 1-element top-level sortlist statement.
+ */
+static int
+query_sortlist_order_1element(const dns_rdata_t *rdata, const void *arg) {
+ isc_netaddr_t netaddr;
+
+ if (rdata_tonetaddr(rdata, &netaddr) != ISC_R_SUCCESS) {
+ return (INT_MAX);
+ }
+ return (ns_sortlist_addrorder1(&netaddr, arg));
+}
+
+/*
+ * Find the sortlist statement that applies to 'client' and set up
+ * the sortlist info in in client->message appropriately.
+ */
+static void
+query_setup_sortlist(query_ctx_t *qctx) {
+ isc_netaddr_t netaddr;
+ ns_client_t *client = qctx->client;
+ dns_aclenv_t *env = client->manager->aclenv;
+ dns_acl_t *acl = NULL;
+ dns_aclelement_t *elt = NULL;
+ void *order_arg = NULL;
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ switch (ns_sortlist_setup(client->view->sortlist, env, &netaddr,
+ &order_arg))
+ {
+ case NS_SORTLISTTYPE_1ELEMENT:
+ elt = order_arg;
+ dns_message_setsortorder(client->message,
+ query_sortlist_order_1element, env,
+ NULL, elt);
+ break;
+ case NS_SORTLISTTYPE_2ELEMENT:
+ acl = order_arg;
+ dns_message_setsortorder(client->message,
+ query_sortlist_order_2element, env,
+ acl, NULL);
+ dns_acl_detach(&acl);
+ break;
+ case NS_SORTLISTTYPE_NONE:
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+/*
+ * When sending a referral, if the answer to the question is
+ * in the glue, sort it to the start of the additional section.
+ */
+static void
+query_glueanswer(query_ctx_t *qctx) {
+ const dns_namelist_t *secs = qctx->client->message->sections;
+ const dns_section_t section = DNS_SECTION_ADDITIONAL;
+ dns_name_t *name;
+ dns_message_t *msg;
+ dns_rdataset_t *rdataset = NULL;
+
+ if (!ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) ||
+ qctx->client->message->rcode != dns_rcode_noerror ||
+ (qctx->qtype != dns_rdatatype_a &&
+ qctx->qtype != dns_rdatatype_aaaa))
+ {
+ return;
+ }
+
+ msg = qctx->client->message;
+ for (name = ISC_LIST_HEAD(msg->sections[section]); name != NULL;
+ name = ISC_LIST_NEXT(name, link))
+ {
+ if (dns_name_equal(name, qctx->client->query.qname)) {
+ for (rdataset = ISC_LIST_HEAD(name->list);
+ rdataset != NULL;
+ rdataset = ISC_LIST_NEXT(rdataset, link))
+ {
+ if (rdataset->type == qctx->qtype) {
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (rdataset != NULL) {
+ ISC_LIST_UNLINK(msg->sections[section], name, link);
+ ISC_LIST_PREPEND(msg->sections[section], name, link);
+ ISC_LIST_UNLINK(name->list, rdataset, link);
+ ISC_LIST_PREPEND(name->list, rdataset, link);
+ rdataset->attributes |= DNS_RDATASETATTR_REQUIRED;
+ }
+}
+
+isc_result_t
+ns_query_done(query_ctx_t *qctx) {
+ isc_result_t result = ISC_R_UNSET;
+ const dns_namelist_t *secs = qctx->client->message->sections;
+ bool nodetach;
+
+ CCTRACE(ISC_LOG_DEBUG(3), "ns_query_done");
+
+ CALL_HOOK(NS_QUERY_DONE_BEGIN, qctx);
+
+ /*
+ * General cleanup.
+ */
+ qctx->rpz_st = qctx->client->query.rpz_st;
+ if (qctx->rpz_st != NULL &&
+ (qctx->rpz_st->state & DNS_RPZ_RECURSING) == 0)
+ {
+ rpz_match_clear(qctx->rpz_st);
+ qctx->rpz_st->state &= ~DNS_RPZ_DONE_QNAME;
+ }
+
+ qctx_clean(qctx);
+ qctx_freedata(qctx);
+
+ if (qctx->client->query.gluedb != NULL) {
+ dns_db_detach(&qctx->client->query.gluedb);
+ }
+
+ /*
+ * Clear the AA bit if we're not authoritative.
+ */
+ if (qctx->client->query.restarts == 0 && !qctx->authoritative) {
+ qctx->client->message->flags &= ~DNS_MESSAGEFLAG_AA;
+ }
+
+ /*
+ * Do we need to restart the query (e.g. for CNAME chaining)?
+ */
+ if (qctx->want_restart && qctx->client->query.restarts < MAX_RESTARTS) {
+ qctx->client->query.restarts++;
+ return (ns__query_start(qctx));
+ }
+
+ if (qctx->result != ISC_R_SUCCESS &&
+ (!PARTIALANSWER(qctx->client) || WANTRECURSION(qctx->client) ||
+ qctx->result == DNS_R_DROP))
+ {
+ if (qctx->result == DNS_R_DUPLICATE ||
+ qctx->result == DNS_R_DROP)
+ {
+ /*
+ * This was a duplicate query that we are
+ * recursing on or the result of rate limiting.
+ * Don't send a response now for a duplicate query,
+ * because the original will still cause a response.
+ */
+ query_next(qctx->client, qctx->result);
+ } else {
+ /*
+ * If we don't have any answer to give the client,
+ * or if the client requested recursion and thus wanted
+ * the complete answer, send an error response.
+ */
+ INSIST(qctx->line >= 0);
+ query_error(qctx->client, qctx->result, qctx->line);
+ }
+
+ qctx->detach_client = true;
+ return (qctx->result);
+ }
+
+ /*
+ * If we're recursing then just return; the query will
+ * resume when recursion ends.
+ */
+ if (RECURSING(qctx->client) &&
+ (!QUERY_STALETIMEOUT(&qctx->client->query) ||
+ ((qctx->options & DNS_GETDB_STALEFIRST) != 0)))
+ {
+ return (qctx->result);
+ }
+
+ /*
+ * We are done. Set up sortlist data for the message
+ * rendering code, sort the answer to the front of the
+ * additional section if necessary, make a final tweak
+ * to the AA bit if the auth-nxdomain config option
+ * says so, then render and send the response.
+ */
+ query_setup_sortlist(qctx);
+ query_glueanswer(qctx);
+
+ if (qctx->client->message->rcode == dns_rcode_nxdomain &&
+ qctx->view->auth_nxdomain)
+ {
+ qctx->client->message->flags |= DNS_MESSAGEFLAG_AA;
+ }
+
+ /*
+ * If the response is somehow unexpected for the client and this
+ * is a result of recursion, return an error to the caller
+ * to indicate it may need to be logged.
+ */
+ if (qctx->resuming &&
+ (ISC_LIST_EMPTY(secs[DNS_SECTION_ANSWER]) ||
+ qctx->client->message->rcode != dns_rcode_noerror))
+ {
+ qctx->result = ISC_R_FAILURE;
+ }
+
+ CALL_HOOK(NS_QUERY_DONE_SEND, qctx);
+
+ /*
+ * Client may have been detached after query_send(), so
+ * we test and store the flag state here, for safety.
+ */
+ nodetach = qctx->client->nodetach;
+ query_send(qctx->client);
+
+ if (qctx->refresh_rrset) {
+ /*
+ * If we reached this point then it means that we have found a
+ * stale RRset entry in cache and BIND is configured to allow
+ * queries to be answered with stale data if no active RRset
+ * is available, i.e. "stale-anwer-client-timeout 0". But, we
+ * still need to refresh the RRset. To prevent adding duplicate
+ * RRsets, clear the RRsets from the message before doing the
+ * refresh.
+ */
+ message_clearrdataset(qctx->client->message, 0);
+ query_refresh_rrset(qctx);
+ }
+
+ if (!nodetach) {
+ qctx->detach_client = true;
+ }
+ return (qctx->result);
+
+cleanup:
+ return (result);
+}
+
+static void
+log_tat(ns_client_t *client) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char clientbuf[ISC_NETADDR_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ isc_netaddr_t netaddr;
+ char *tags = NULL;
+ size_t taglen = 0;
+
+ if (!isc_log_wouldlog(ns_lctx, ISC_LOG_INFO)) {
+ return;
+ }
+
+ if ((client->query.qtype != dns_rdatatype_null ||
+ !dns_name_istat(client->query.qname)) &&
+ (client->keytag == NULL ||
+ client->query.qtype != dns_rdatatype_dnskey))
+ {
+ return;
+ }
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
+ isc_netaddr_format(&netaddr, clientbuf, sizeof(clientbuf));
+ dns_rdataclass_format(client->view->rdclass, classbuf,
+ sizeof(classbuf));
+
+ if (client->query.qtype == dns_rdatatype_dnskey) {
+ uint16_t keytags = client->keytag_len / 2;
+ size_t len = taglen = sizeof("65000") * keytags + 1;
+ char *cp = tags = isc_mem_get(client->mctx, taglen);
+ int i = 0;
+
+ INSIST(client->keytag != NULL);
+ if (tags != NULL) {
+ while (keytags-- > 0U) {
+ int n;
+ uint16_t keytag;
+ keytag = (client->keytag[i * 2] << 8) |
+ client->keytag[i * 2 + 1];
+ n = snprintf(cp, len, " %u", keytag);
+ if (n > 0 && (size_t)n <= len) {
+ cp += n;
+ len -= n;
+ i++;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_TAT, NS_LOGMODULE_QUERY,
+ ISC_LOG_INFO, "trust-anchor-telemetry '%s/%s' from %s%s",
+ namebuf, classbuf, clientbuf, tags != NULL ? tags : "");
+ if (tags != NULL) {
+ isc_mem_put(client->mctx, tags, taglen);
+ }
+}
+
+static void
+log_query(ns_client_t *client, unsigned int flags, unsigned int extflags) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ char onbuf[ISC_NETADDR_FORMATSIZE];
+ char ecsbuf[DNS_ECS_FORMATSIZE + sizeof(" [ECS ]") - 1] = { 0 };
+ char ednsbuf[sizeof("E(65535)")] = { 0 };
+ dns_rdataset_t *rdataset;
+ int level = ISC_LOG_INFO;
+
+ if (!isc_log_wouldlog(ns_lctx, level)) {
+ return;
+ }
+
+ rdataset = ISC_LIST_HEAD(client->query.qname->list);
+ INSIST(rdataset != NULL);
+ dns_name_format(client->query.qname, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(rdataset->rdclass, classbuf, sizeof(classbuf));
+ dns_rdatatype_format(rdataset->type, typebuf, sizeof(typebuf));
+ isc_netaddr_format(&client->destaddr, onbuf, sizeof(onbuf));
+
+ if (client->ednsversion >= 0) {
+ snprintf(ednsbuf, sizeof(ednsbuf), "E(%hd)",
+ client->ednsversion);
+ }
+
+ if (HAVEECS(client)) {
+ strlcpy(ecsbuf, " [ECS ", sizeof(ecsbuf));
+ dns_ecs_format(&client->ecs, ecsbuf + 6, sizeof(ecsbuf) - 6);
+ strlcat(ecsbuf, "]", sizeof(ecsbuf));
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_QUERIES, NS_LOGMODULE_QUERY, level,
+ "query: %s %s %s %s%s%s%s%s%s%s (%s)%s", namebuf,
+ classbuf, typebuf, WANTRECURSION(client) ? "+" : "-",
+ (client->signer != NULL) ? "S" : "", ednsbuf,
+ TCP(client) ? "T" : "",
+ ((extflags & DNS_MESSAGEEXTFLAG_DO) != 0) ? "D" : "",
+ ((flags & DNS_MESSAGEFLAG_CD) != 0) ? "C" : "",
+ HAVECOOKIE(client) ? "V"
+ : WANTCOOKIE(client) ? "K"
+ : "",
+ onbuf, ecsbuf);
+}
+
+static void
+log_queryerror(ns_client_t *client, isc_result_t result, int line, int level) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ const char *namep, *typep, *classp, *sep1, *sep2;
+ dns_rdataset_t *rdataset;
+
+ if (!isc_log_wouldlog(ns_lctx, level)) {
+ return;
+ }
+
+ namep = typep = classp = sep1 = sep2 = "";
+
+ /*
+ * Query errors can happen for various reasons. In some cases we cannot
+ * even assume the query contains a valid question section, so we should
+ * expect exceptional cases.
+ */
+ if (client->query.origqname != NULL) {
+ dns_name_format(client->query.origqname, namebuf,
+ sizeof(namebuf));
+ namep = namebuf;
+ sep1 = " for ";
+
+ rdataset = ISC_LIST_HEAD(client->query.origqname->list);
+ if (rdataset != NULL) {
+ dns_rdataclass_format(rdataset->rdclass, classbuf,
+ sizeof(classbuf));
+ classp = classbuf;
+ dns_rdatatype_format(rdataset->type, typebuf,
+ sizeof(typebuf));
+ typep = typebuf;
+ sep2 = "/";
+ }
+ }
+
+ ns_client_log(client, NS_LOGCATEGORY_QUERY_ERRORS, NS_LOGMODULE_QUERY,
+ level, "query failed (%s)%s%s%s%s%s%s at %s:%d",
+ isc_result_totext(result), sep1, namep, sep2, classp,
+ sep2, typep, __FILE__, line);
+}
+
+void
+ns_query_start(ns_client_t *client, isc_nmhandle_t *handle) {
+ isc_result_t result;
+ dns_message_t *message;
+ dns_rdataset_t *rdataset;
+ dns_rdatatype_t qtype;
+ unsigned int saved_extflags;
+ unsigned int saved_flags;
+
+ REQUIRE(NS_CLIENT_VALID(client));
+
+ /*
+ * Attach to the request handle
+ */
+ isc_nmhandle_attach(handle, &client->reqhandle);
+
+ message = client->message;
+ saved_extflags = client->extflags;
+ saved_flags = client->message->flags;
+
+ CTRACE(ISC_LOG_DEBUG(3), "ns_query_start");
+
+ /*
+ * Ensure that appropriate cleanups occur.
+ */
+ client->cleanup = query_cleanup;
+
+ if ((message->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ client->query.attributes |= NS_QUERYATTR_WANTRECURSION;
+ }
+
+ if ((client->extflags & DNS_MESSAGEEXTFLAG_DO) != 0) {
+ client->attributes |= NS_CLIENTATTR_WANTDNSSEC;
+ }
+
+ switch (client->view->minimalresponses) {
+ case dns_minimal_no:
+ break;
+ case dns_minimal_yes:
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+ break;
+ case dns_minimal_noauth:
+ client->query.attributes |= NS_QUERYATTR_NOAUTHORITY;
+ break;
+ case dns_minimal_noauthrec:
+ if ((message->flags & DNS_MESSAGEFLAG_RD) != 0) {
+ client->query.attributes |= NS_QUERYATTR_NOAUTHORITY;
+ }
+ break;
+ }
+
+ if (client->view->cachedb == NULL || !client->view->recursion) {
+ /*
+ * We don't have a cache. Turn off cache support and
+ * recursion.
+ */
+ client->query.attributes &= ~(NS_QUERYATTR_RECURSIONOK |
+ NS_QUERYATTR_CACHEOK);
+ client->attributes |= NS_CLIENTATTR_NOSETFC;
+ } else if ((client->attributes & NS_CLIENTATTR_RA) == 0 ||
+ (message->flags & DNS_MESSAGEFLAG_RD) == 0)
+ {
+ /*
+ * If the client isn't allowed to recurse (due to
+ * "recursion no", the allow-recursion ACL, or the
+ * lack of a resolver in this view), or if it
+ * doesn't want recursion, turn recursion off.
+ */
+ client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK;
+ client->attributes |= NS_CLIENTATTR_NOSETFC;
+ }
+
+ /*
+ * Check for multiple question queries, since edns1 is dead.
+ */
+ if (message->counts[DNS_SECTION_QUESTION] > 1) {
+ query_error(client, DNS_R_FORMERR, __LINE__);
+ return;
+ }
+
+ /*
+ * Get the question name.
+ */
+ result = dns_message_firstname(message, DNS_SECTION_QUESTION);
+ if (result != ISC_R_SUCCESS) {
+ query_error(client, result, __LINE__);
+ return;
+ }
+ dns_message_currentname(message, DNS_SECTION_QUESTION,
+ &client->query.qname);
+ client->query.origqname = client->query.qname;
+ result = dns_message_nextname(message, DNS_SECTION_QUESTION);
+ if (result != ISC_R_NOMORE) {
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * There's more than one QNAME in the question
+ * section.
+ */
+ query_error(client, DNS_R_FORMERR, __LINE__);
+ } else {
+ query_error(client, result, __LINE__);
+ }
+ return;
+ }
+
+ if ((client->sctx->options & NS_SERVER_LOGQUERIES) != 0) {
+ log_query(client, saved_flags, saved_extflags);
+ }
+
+ /*
+ * Check for meta-queries like IXFR and AXFR.
+ */
+ rdataset = ISC_LIST_HEAD(client->query.qname->list);
+ INSIST(rdataset != NULL);
+ client->query.qtype = qtype = rdataset->type;
+ dns_rdatatypestats_increment(client->sctx->rcvquerystats, qtype);
+
+ log_tat(client);
+
+ if (dns_rdatatype_ismeta(qtype)) {
+ switch (qtype) {
+ case dns_rdatatype_any:
+ break; /* Let the query logic handle it. */
+ case dns_rdatatype_ixfr:
+ case dns_rdatatype_axfr:
+ if (isc_nm_is_http_handle(handle)) {
+ /*
+ * We cannot use DoH for zone transfers.
+ * According to RFC 8484 a DoH request contains
+ * exactly one DNS message (see Section 6:
+ * Definition of the "application/dns-message"
+ * Media Type).
+ *
+ * This makes DoH unsuitable for zone transfers
+ * as often (and usually!) these need more than
+ * one DNS message, especially for larger zones.
+ * As zone transfers over DoH are not (yet)
+ * standardised, nor discussed in RFC 8484,
+ * the best thing we can do is to return "not
+ * implemented".
+ */
+ query_error(client, DNS_R_NOTIMP, __LINE__);
+ return;
+ }
+ if (isc_nm_socket_type(handle) == isc_nm_tlsdnssocket) {
+ /*
+ * Currently this code is here for DoT, which
+ * has more complex requirements for zone
+ * transfers compared to other stream
+ * protocols. See RFC 9103 for details.
+ */
+ switch (isc_nm_xfr_checkperm(handle)) {
+ case ISC_R_SUCCESS:
+ break;
+ case ISC_R_DOTALPNERROR:
+ query_error(client, DNS_R_NOALPN,
+ __LINE__);
+ return;
+ default:
+ query_error(client, DNS_R_REFUSED,
+ __LINE__);
+ return;
+ }
+ }
+ ns_xfr_start(client, rdataset->type);
+ return;
+ case dns_rdatatype_maila:
+ case dns_rdatatype_mailb:
+ query_error(client, DNS_R_NOTIMP, __LINE__);
+ return;
+ case dns_rdatatype_tkey:
+ result = dns_tkey_processquery(
+ client->message, client->sctx->tkeyctx,
+ client->view->dynamickeys);
+ if (result == ISC_R_SUCCESS) {
+ query_send(client);
+ } else {
+ query_error(client, result, __LINE__);
+ }
+ return;
+ default: /* TSIG, etc. */
+ query_error(client, DNS_R_FORMERR, __LINE__);
+ return;
+ }
+ }
+
+ /*
+ * Turn on minimal response for (C)DNSKEY and (C)DS queries.
+ */
+ if (qtype == dns_rdatatype_dnskey || qtype == dns_rdatatype_ds ||
+ qtype == dns_rdatatype_cdnskey || qtype == dns_rdatatype_cds)
+ {
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+ } else if (qtype == dns_rdatatype_ns) {
+ /*
+ * Always turn on additional records for NS queries.
+ */
+ client->query.attributes &= ~(NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+ }
+
+ /*
+ * Maybe turn on minimal responses for ANY queries.
+ */
+ if (qtype == dns_rdatatype_any && client->view->minimal_any &&
+ !TCP(client))
+ {
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+ }
+
+ /*
+ * Turn on minimal responses for EDNS/UDP bufsize 512 queries.
+ */
+ if (client->ednsversion >= 0 && client->udpsize <= 512U && !TCP(client))
+ {
+ client->query.attributes |= (NS_QUERYATTR_NOAUTHORITY |
+ NS_QUERYATTR_NOADDITIONAL);
+ }
+
+ /*
+ * If the client has requested that DNSSEC checking be disabled,
+ * allow lookups to return pending data and instruct the resolver
+ * to return data before validation has completed.
+ *
+ * We don't need to set DNS_DBFIND_PENDINGOK when validation is
+ * disabled as there will be no pending data.
+ */
+ if ((message->flags & DNS_MESSAGEFLAG_CD) != 0 ||
+ qtype == dns_rdatatype_rrsig)
+ {
+ client->query.dboptions |= DNS_DBFIND_PENDINGOK;
+ client->query.fetchoptions |= DNS_FETCHOPT_NOVALIDATE;
+ } else if (!client->view->enablevalidation) {
+ client->query.fetchoptions |= DNS_FETCHOPT_NOVALIDATE;
+ }
+
+ if (client->view->qminimization) {
+ client->query.fetchoptions |= DNS_FETCHOPT_QMINIMIZE |
+ DNS_FETCHOPT_QMIN_SKIP_IP6A;
+ if (client->view->qmin_strict) {
+ client->query.fetchoptions |= DNS_FETCHOPT_QMIN_STRICT;
+ }
+ }
+
+ /*
+ * Allow glue NS records to be added to the authority section
+ * if the answer is secure.
+ */
+ if ((message->flags & DNS_MESSAGEFLAG_CD) != 0) {
+ client->query.attributes &= ~NS_QUERYATTR_SECURE;
+ }
+
+ /*
+ * Set NS_CLIENTATTR_WANTAD if the client has set AD in the query.
+ * This allows AD to be returned on queries without DO set.
+ */
+ if ((message->flags & DNS_MESSAGEFLAG_AD) != 0) {
+ client->attributes |= NS_CLIENTATTR_WANTAD;
+ }
+
+ /*
+ * This is an ordinary query.
+ */
+ result = dns_message_reply(message, true);
+ if (result != ISC_R_SUCCESS) {
+ query_next(client, result);
+ return;
+ }
+
+ /*
+ * Assume authoritative response until it is known to be
+ * otherwise.
+ *
+ * If "-T noaa" has been set on the command line don't set
+ * AA on authoritative answers.
+ */
+ if ((client->sctx->options & NS_SERVER_NOAA) == 0) {
+ message->flags |= DNS_MESSAGEFLAG_AA;
+ }
+
+ /*
+ * Set AD. We must clear it if we add non-validated data to a
+ * response.
+ */
+ if (WANTDNSSEC(client) || WANTAD(client)) {
+ message->flags |= DNS_MESSAGEFLAG_AD;
+ }
+
+ (void)query_setup(client, qtype);
+}
diff --git a/lib/ns/server.c b/lib/ns/server.c
new file mode 100644
index 0000000..7b245c3
--- /dev/null
+++ b/lib/ns/server.c
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <stdbool.h>
+
+#include <isc/mem.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include <dns/stats.h>
+#include <dns/tkey.h>
+
+#include <ns/query.h>
+#include <ns/server.h>
+#include <ns/stats.h>
+
+#define SCTX_MAGIC ISC_MAGIC('S', 'c', 't', 'x')
+#define SCTX_VALID(s) ISC_MAGIC_VALID(s, SCTX_MAGIC)
+
+#define CHECKFATAL(op) \
+ do { \
+ result = (op); \
+ RUNTIME_CHECK(result == ISC_R_SUCCESS); \
+ } while (0)
+
+isc_result_t
+ns_server_create(isc_mem_t *mctx, ns_matchview_t matchingview,
+ ns_server_t **sctxp) {
+ ns_server_t *sctx;
+ isc_result_t result;
+
+ REQUIRE(sctxp != NULL && *sctxp == NULL);
+
+ sctx = isc_mem_get(mctx, sizeof(*sctx));
+
+ memset(sctx, 0, sizeof(*sctx));
+
+ isc_mem_attach(mctx, &sctx->mctx);
+
+ /*
+ * See here for more details:
+ * https://github.com/jemalloc/jemalloc/issues/2483
+ */
+
+ isc_refcount_init(&sctx->references, 1);
+
+ isc_quota_init(&sctx->xfroutquota, 10);
+ isc_quota_init(&sctx->tcpquota, 10);
+ isc_quota_init(&sctx->recursionquota, 100);
+ isc_quota_init(&sctx->updquota, 100);
+ ISC_LIST_INIT(sctx->http_quotas);
+ isc_mutex_init(&sctx->http_quotas_lock);
+
+ CHECKFATAL(dns_tkeyctx_create(mctx, &sctx->tkeyctx));
+
+ CHECKFATAL(ns_stats_create(mctx, ns_statscounter_max, &sctx->nsstats));
+
+ CHECKFATAL(dns_rdatatypestats_create(mctx, &sctx->rcvquerystats));
+
+ CHECKFATAL(dns_opcodestats_create(mctx, &sctx->opcodestats));
+
+ CHECKFATAL(dns_rcodestats_create(mctx, &sctx->rcodestats));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->udpinstats4,
+ dns_sizecounter_in_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->udpoutstats4,
+ dns_sizecounter_out_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->udpinstats6,
+ dns_sizecounter_in_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->udpoutstats6,
+ dns_sizecounter_out_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->tcpinstats4,
+ dns_sizecounter_in_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->tcpoutstats4,
+ dns_sizecounter_out_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->tcpinstats6,
+ dns_sizecounter_in_max));
+
+ CHECKFATAL(isc_stats_create(mctx, &sctx->tcpoutstats6,
+ dns_sizecounter_out_max));
+
+ sctx->udpsize = 1232;
+ sctx->transfer_tcp_message_size = 20480;
+
+ sctx->fuzztype = isc_fuzz_none;
+ sctx->fuzznotify = NULL;
+
+ sctx->matchingview = matchingview;
+ sctx->answercookie = true;
+
+ ISC_LIST_INIT(sctx->altsecrets);
+
+ sctx->magic = SCTX_MAGIC;
+ *sctxp = sctx;
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_server_attach(ns_server_t *src, ns_server_t **dest) {
+ REQUIRE(SCTX_VALID(src));
+ REQUIRE(dest != NULL && *dest == NULL);
+
+ isc_refcount_increment(&src->references);
+
+ *dest = src;
+}
+
+void
+ns_server_detach(ns_server_t **sctxp) {
+ ns_server_t *sctx;
+
+ REQUIRE(sctxp != NULL && SCTX_VALID(*sctxp));
+ sctx = *sctxp;
+ *sctxp = NULL;
+
+ if (isc_refcount_decrement(&sctx->references) == 1) {
+ ns_altsecret_t *altsecret;
+ isc_quota_t *http_quota;
+
+ while ((altsecret = ISC_LIST_HEAD(sctx->altsecrets)) != NULL) {
+ ISC_LIST_UNLINK(sctx->altsecrets, altsecret, link);
+ isc_mem_put(sctx->mctx, altsecret, sizeof(*altsecret));
+ }
+
+ isc_quota_destroy(&sctx->updquota);
+ isc_quota_destroy(&sctx->recursionquota);
+ isc_quota_destroy(&sctx->tcpquota);
+ isc_quota_destroy(&sctx->xfroutquota);
+
+ http_quota = ISC_LIST_HEAD(sctx->http_quotas);
+ while (http_quota != NULL) {
+ isc_quota_t *next = NULL;
+
+ next = ISC_LIST_NEXT(http_quota, link);
+ ISC_LIST_DEQUEUE(sctx->http_quotas, http_quota, link);
+ isc_quota_destroy(http_quota);
+ isc_mem_put(sctx->mctx, http_quota,
+ sizeof(*http_quota));
+ http_quota = next;
+ }
+ isc_mutex_destroy(&sctx->http_quotas_lock);
+
+ if (sctx->server_id != NULL) {
+ isc_mem_free(sctx->mctx, sctx->server_id);
+ }
+
+ if (sctx->blackholeacl != NULL) {
+ dns_acl_detach(&sctx->blackholeacl);
+ }
+ if (sctx->keepresporder != NULL) {
+ dns_acl_detach(&sctx->keepresporder);
+ }
+ if (sctx->tkeyctx != NULL) {
+ dns_tkeyctx_destroy(&sctx->tkeyctx);
+ }
+
+ if (sctx->nsstats != NULL) {
+ ns_stats_detach(&sctx->nsstats);
+ }
+
+ if (sctx->rcvquerystats != NULL) {
+ dns_stats_detach(&sctx->rcvquerystats);
+ }
+ if (sctx->opcodestats != NULL) {
+ dns_stats_detach(&sctx->opcodestats);
+ }
+ if (sctx->rcodestats != NULL) {
+ dns_stats_detach(&sctx->rcodestats);
+ }
+
+ if (sctx->udpinstats4 != NULL) {
+ isc_stats_detach(&sctx->udpinstats4);
+ }
+ if (sctx->tcpinstats4 != NULL) {
+ isc_stats_detach(&sctx->tcpinstats4);
+ }
+ if (sctx->udpoutstats4 != NULL) {
+ isc_stats_detach(&sctx->udpoutstats4);
+ }
+ if (sctx->tcpoutstats4 != NULL) {
+ isc_stats_detach(&sctx->tcpoutstats4);
+ }
+
+ if (sctx->udpinstats6 != NULL) {
+ isc_stats_detach(&sctx->udpinstats6);
+ }
+ if (sctx->tcpinstats6 != NULL) {
+ isc_stats_detach(&sctx->tcpinstats6);
+ }
+ if (sctx->udpoutstats6 != NULL) {
+ isc_stats_detach(&sctx->udpoutstats6);
+ }
+ if (sctx->tcpoutstats6 != NULL) {
+ isc_stats_detach(&sctx->tcpoutstats6);
+ }
+
+ sctx->magic = 0;
+
+ isc_mem_putanddetach(&sctx->mctx, sctx, sizeof(*sctx));
+ }
+}
+
+isc_result_t
+ns_server_setserverid(ns_server_t *sctx, const char *serverid) {
+ REQUIRE(SCTX_VALID(sctx));
+
+ if (sctx->server_id != NULL) {
+ isc_mem_free(sctx->mctx, sctx->server_id);
+ sctx->server_id = NULL;
+ }
+
+ if (serverid != NULL) {
+ sctx->server_id = isc_mem_strdup(sctx->mctx, serverid);
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+void
+ns_server_setoption(ns_server_t *sctx, unsigned int option, bool value) {
+ REQUIRE(SCTX_VALID(sctx));
+ if (value) {
+ sctx->options |= option;
+ } else {
+ sctx->options &= ~option;
+ }
+}
+
+bool
+ns_server_getoption(ns_server_t *sctx, unsigned int option) {
+ REQUIRE(SCTX_VALID(sctx));
+
+ return ((sctx->options & option) != 0);
+}
+
+void
+ns_server_append_http_quota(ns_server_t *sctx, isc_quota_t *http_quota) {
+ REQUIRE(SCTX_VALID(sctx));
+ REQUIRE(http_quota != NULL);
+
+ LOCK(&sctx->http_quotas_lock);
+ ISC_LINK_INIT(http_quota, link);
+ ISC_LIST_APPEND(sctx->http_quotas, http_quota, link);
+ UNLOCK(&sctx->http_quotas_lock);
+}
diff --git a/lib/ns/sortlist.c b/lib/ns/sortlist.c
new file mode 100644
index 0000000..2aada90
--- /dev/null
+++ b/lib/ns/sortlist.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/mem.h>
+#include <isc/result.h>
+#include <isc/util.h>
+
+#include <dns/acl.h>
+#include <dns/message.h>
+
+#include <ns/server.h>
+#include <ns/sortlist.h>
+
+ns_sortlisttype_t
+ns_sortlist_setup(dns_acl_t *acl, dns_aclenv_t *env, isc_netaddr_t *clientaddr,
+ void **argp) {
+ if (acl == NULL) {
+ goto dont_sort;
+ }
+
+ for (size_t i = 0; i < acl->length; i++) {
+ /*
+ * 'e' refers to the current 'top level statement'
+ * in the sortlist (see ARM).
+ */
+ dns_aclelement_t *e = &acl->elements[i];
+ dns_aclelement_t *try_elt;
+ dns_aclelement_t *order_elt = NULL;
+ dns_aclelement_t *matched_elt = NULL;
+
+ if (e->type == dns_aclelementtype_nestedacl) {
+ dns_acl_t *inner = e->nestedacl;
+
+ if (inner->length == 0) {
+ try_elt = e;
+ } else if (inner->length > 2) {
+ goto dont_sort;
+ } else if (inner->elements[0].negative) {
+ goto dont_sort;
+ } else {
+ try_elt = &inner->elements[0];
+ if (inner->length == 2) {
+ order_elt = &inner->elements[1];
+ }
+ }
+ } else {
+ /*
+ * BIND 8 allows bare elements at the top level
+ * as an undocumented feature.
+ */
+ try_elt = e;
+ }
+
+ if (!dns_aclelement_match(
+ clientaddr, NULL, try_elt, env,
+ (const dns_aclelement_t **)&matched_elt))
+ {
+ continue;
+ }
+
+ if (order_elt == NULL) {
+ INSIST(matched_elt != NULL);
+ *argp = matched_elt;
+ return (NS_SORTLISTTYPE_1ELEMENT);
+ }
+
+ if (order_elt->type == dns_aclelementtype_nestedacl) {
+ dns_acl_t *inner = NULL;
+ dns_acl_attach(order_elt->nestedacl, &inner);
+ *argp = inner;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ }
+
+ if (order_elt->type == dns_aclelementtype_localhost) {
+ dns_acl_t *inner = NULL;
+ RWLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (env->localhost != NULL) {
+ dns_acl_attach(env->localhost, &inner);
+ }
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+
+ if (inner != NULL) {
+ *argp = inner;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ }
+ }
+
+ if (order_elt->type == dns_aclelementtype_localnets) {
+ dns_acl_t *inner = NULL;
+ RWLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (env->localnets != NULL) {
+ dns_acl_attach(env->localnets, &inner);
+ }
+ RWUNLOCK(&env->rwlock, isc_rwlocktype_read);
+ if (inner != NULL) {
+ *argp = inner;
+ return (NS_SORTLISTTYPE_2ELEMENT);
+ }
+ }
+
+ /*
+ * BIND 8 allows a bare IP prefix as
+ * the 2nd element of a 2-element
+ * sortlist statement.
+ */
+ *argp = order_elt;
+ return (NS_SORTLISTTYPE_1ELEMENT);
+ }
+
+dont_sort:
+ *argp = NULL;
+ return (NS_SORTLISTTYPE_NONE);
+}
+
+int
+ns_sortlist_addrorder2(const isc_netaddr_t *addr, const void *arg) {
+ const dns_sortlist_arg_t *sla = (const dns_sortlist_arg_t *)arg;
+ dns_aclenv_t *env = sla->env;
+ const dns_acl_t *sortacl = sla->acl;
+ int match;
+
+ (void)dns_acl_match(addr, NULL, sortacl, env, &match, NULL);
+ if (match > 0) {
+ return (match);
+ } else if (match < 0) {
+ return (INT_MAX - (-match));
+ } else {
+ return (INT_MAX / 2);
+ }
+}
+
+int
+ns_sortlist_addrorder1(const isc_netaddr_t *addr, const void *arg) {
+ const dns_sortlist_arg_t *sla = (const dns_sortlist_arg_t *)arg;
+ dns_aclenv_t *env = sla->env;
+ const dns_aclelement_t *element = sla->element;
+
+ if (dns_aclelement_match(addr, NULL, element, env, NULL)) {
+ return (0);
+ }
+
+ return (INT_MAX);
+}
+
+void
+ns_sortlist_byaddrsetup(dns_acl_t *sortlist_acl, dns_aclenv_t *env,
+ isc_netaddr_t *client_addr,
+ dns_addressorderfunc_t *orderp, void **argp) {
+ ns_sortlisttype_t sortlisttype;
+
+ sortlisttype = ns_sortlist_setup(sortlist_acl, env, client_addr, argp);
+
+ switch (sortlisttype) {
+ case NS_SORTLISTTYPE_1ELEMENT:
+ *orderp = ns_sortlist_addrorder1;
+ break;
+ case NS_SORTLISTTYPE_2ELEMENT:
+ *orderp = ns_sortlist_addrorder2;
+ break;
+ case NS_SORTLISTTYPE_NONE:
+ *orderp = NULL;
+ break;
+ default:
+ UNEXPECTED_ERROR(
+ "unexpected return from ns_sortlist_setup(): %d",
+ sortlisttype);
+ break;
+ }
+}
diff --git a/lib/ns/stats.c b/lib/ns/stats.c
new file mode 100644
index 0000000..de5b083
--- /dev/null
+++ b/lib/ns/stats.c
@@ -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.
+ */
+
+/*! \file */
+
+#include <isc/magic.h>
+#include <isc/mem.h>
+#include <isc/refcount.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include <ns/stats.h>
+
+#define NS_STATS_MAGIC ISC_MAGIC('N', 's', 't', 't')
+#define NS_STATS_VALID(x) ISC_MAGIC_VALID(x, NS_STATS_MAGIC)
+
+struct ns_stats {
+ /*% Unlocked */
+ unsigned int magic;
+ isc_mem_t *mctx;
+ isc_stats_t *counters;
+ isc_refcount_t references;
+};
+
+void
+ns_stats_attach(ns_stats_t *stats, ns_stats_t **statsp) {
+ REQUIRE(NS_STATS_VALID(stats));
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ isc_refcount_increment(&stats->references);
+
+ *statsp = stats;
+}
+
+void
+ns_stats_detach(ns_stats_t **statsp) {
+ ns_stats_t *stats;
+
+ REQUIRE(statsp != NULL && NS_STATS_VALID(*statsp));
+
+ stats = *statsp;
+ *statsp = NULL;
+
+ if (isc_refcount_decrement(&stats->references) == 1) {
+ isc_stats_detach(&stats->counters);
+ isc_refcount_destroy(&stats->references);
+ isc_mem_putanddetach(&stats->mctx, stats, sizeof(*stats));
+ }
+}
+
+isc_result_t
+ns_stats_create(isc_mem_t *mctx, int ncounters, ns_stats_t **statsp) {
+ ns_stats_t *stats;
+ isc_result_t result;
+
+ REQUIRE(statsp != NULL && *statsp == NULL);
+
+ 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_mem;
+ }
+
+ stats->magic = NS_STATS_MAGIC;
+ stats->mctx = NULL;
+ isc_mem_attach(mctx, &stats->mctx);
+ *statsp = stats;
+
+ return (ISC_R_SUCCESS);
+
+clean_mem:
+ isc_mem_put(mctx, stats, sizeof(*stats));
+
+ return (result);
+}
+
+/*%
+ * Increment/Decrement methods
+ */
+void
+ns_stats_increment(ns_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(NS_STATS_VALID(stats));
+
+ isc_stats_increment(stats->counters, counter);
+}
+
+void
+ns_stats_decrement(ns_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(NS_STATS_VALID(stats));
+
+ isc_stats_decrement(stats->counters, counter);
+}
+
+isc_stats_t *
+ns_stats_get(ns_stats_t *stats) {
+ REQUIRE(NS_STATS_VALID(stats));
+
+ return (stats->counters);
+}
+
+void
+ns_stats_update_if_greater(ns_stats_t *stats, isc_statscounter_t counter,
+ isc_statscounter_t value) {
+ REQUIRE(NS_STATS_VALID(stats));
+
+ isc_stats_update_if_greater(stats->counters, counter, value);
+}
+
+isc_statscounter_t
+ns_stats_get_counter(ns_stats_t *stats, isc_statscounter_t counter) {
+ REQUIRE(NS_STATS_VALID(stats));
+
+ return (isc_stats_get_counter(stats->counters, counter));
+}
diff --git a/lib/ns/update.c b/lib/ns/update.c
new file mode 100644
index 0000000..983ca84
--- /dev/null
+++ b/lib/ns/update.c
@@ -0,0 +1,3866 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/netaddr.h>
+#include <isc/print.h>
+#include <isc/serial.h>
+#include <isc/stats.h>
+#include <isc/string.h>
+#include <isc/taskpool.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/keyvalues.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/result.h>
+#include <dns/soa.h>
+#include <dns/ssu.h>
+#include <dns/tsig.h>
+#include <dns/update.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include <ns/client.h>
+#include <ns/interfacemgr.h>
+#include <ns/log.h>
+#include <ns/server.h>
+#include <ns/stats.h>
+#include <ns/update.h>
+
+/*! \file
+ * \brief
+ * This module implements dynamic update as in RFC2136.
+ */
+
+/*
+ * XXX TODO:
+ * - document strict minimality
+ */
+
+/**************************************************************************/
+
+/*%
+ * 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"; \
+ default: \
+ break; \
+ } \
+ update_log(client, zone, LOGLEVEL_PROTOCOL, \
+ "update %s: %s (%s)", _what, msg, \
+ isc_result_totext(result)); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+#define PREREQFAILC(code, msg) \
+ do { \
+ inc_stats(client, zone, ns_statscounter_updatebadprereq); \
+ FAILC(code, msg); \
+ } 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"; \
+ default: \
+ break; \
+ } \
+ if (isc_log_wouldlog(ns_lctx, LOGLEVEL_PROTOCOL)) { \
+ char _nbuf[DNS_NAME_FORMATSIZE]; \
+ dns_name_format(name, _nbuf, sizeof(_nbuf)); \
+ update_log(client, 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 PREREQFAILN(code, name, msg) \
+ do { \
+ inc_stats(client, zone, ns_statscounter_updatebadprereq); \
+ FAILN(code, name, msg); \
+ } 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"; \
+ default: \
+ break; \
+ } \
+ if (isc_log_wouldlog(ns_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(client, 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)
+#define PREREQFAILNT(code, name, type, msg) \
+ do { \
+ inc_stats(client, zone, ns_statscounter_updatebadprereq); \
+ FAILNT(code, name, type, msg); \
+ } 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(client, zone, LOGLEVEL_PROTOCOL, "error: %s: %s", \
+ msg, isc_result_totext(result)); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+/*
+ * Return TRUE if NS_CLIENTATTR_TCP is set in the attributes other FALSE.
+ */
+#define TCPCLIENT(client) (((client)->attributes & NS_CLIENTATTR_TCP) != 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;
+
+struct update_event {
+ ISC_EVENT_COMMON(update_event_t);
+ dns_zone_t *zone;
+ isc_result_t result;
+ dns_message_t *answer;
+ const dns_ssurule_t **rules;
+ size_t ruleslen;
+};
+
+/*%
+ * Prepare an RR for the addition of the new RR 'ctx->update_rr',
+ * with TTL 'ctx->update_rr_ttl', to its rdataset, by deleting
+ * the RRs if it is replaced by the new RR or has a conflicting TTL.
+ * The necessary changes are appended to ctx->del_diff and ctx->add_diff;
+ * we need to do all deletions before any additions so that we don't run
+ * into transient states with conflicting TTLs.
+ */
+
+typedef struct {
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ dns_diff_t *diff;
+ dns_name_t *name;
+ dns_name_t *oldname;
+ dns_rdata_t *update_rr;
+ dns_ttl_t update_rr_ttl;
+ bool ignore_add;
+ dns_diff_t del_diff;
+ dns_diff_t add_diff;
+} add_rr_prepare_ctx_t;
+
+/**************************************************************************/
+/*
+ * Forward declarations.
+ */
+
+static void
+update_action(isc_task_t *task, isc_event_t *event);
+static void
+updatedone_action(isc_task_t *task, isc_event_t *event);
+static isc_result_t
+send_forward_event(ns_client_t *client, dns_zone_t *zone);
+static void
+forward_done(isc_task_t *task, isc_event_t *event);
+static isc_result_t
+add_rr_prepare_action(void *data, rr_t *rr);
+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);
+
+/**************************************************************************/
+
+static void
+update_log(ns_client_t *client, dns_zone_t *zone, int level, const char *fmt,
+ ...) ISC_FORMAT_PRINTF(4, 5);
+
+static void
+update_log(ns_client_t *client, dns_zone_t *zone, int level, const char *fmt,
+ ...) {
+ va_list ap;
+ char message[4096];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ if (client == NULL) {
+ return;
+ }
+
+ if (!isc_log_wouldlog(ns_lctx, level)) {
+ return;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(message, sizeof(message), fmt, ap);
+ va_end(ap);
+
+ if (zone != NULL) {
+ dns_name_format(dns_zone_getorigin(zone), namebuf,
+ sizeof(namebuf));
+ dns_rdataclass_format(dns_zone_getclass(zone), classbuf,
+ sizeof(classbuf));
+
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE,
+ NS_LOGMODULE_UPDATE, level,
+ "updating zone '%s/%s': %s", namebuf, classbuf,
+ message);
+ } else {
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE,
+ NS_LOGMODULE_UPDATE, level, "%s", message);
+ }
+}
+
+static void
+update_log_cb(void *arg, dns_zone_t *zone, int level, const char *message) {
+ update_log(arg, zone, level, "%s", message);
+}
+
+/*%
+ * Increment updated-related statistics counters.
+ */
+static void
+inc_stats(ns_client_t *client, dns_zone_t *zone, isc_statscounter_t counter) {
+ ns_stats_increment(client->sctx->nsstats, counter);
+
+ if (zone != NULL) {
+ isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
+ if (zonestats != NULL) {
+ isc_stats_increment(zonestats, counter);
+ }
+ }
+}
+
+/*%
+ * Check if we could have queried for the contents of this zone or
+ * if the zone is potentially updateable.
+ * If the zone can potentially be updated and the check failed then
+ * log a error otherwise we log a informational message.
+ */
+static isc_result_t
+checkqueryacl(ns_client_t *client, dns_acl_t *queryacl, dns_name_t *zonename,
+ dns_acl_t *updateacl, dns_ssutable_t *ssutable) {
+ isc_result_t result;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ bool update_possible =
+ ((updateacl != NULL && !dns_acl_isnone(updateacl)) ||
+ ssutable != NULL);
+
+ result = ns_client_checkaclsilent(client, NULL, queryacl, true);
+ if (result != ISC_R_SUCCESS) {
+ int level = update_possible ? ISC_LOG_ERROR : ISC_LOG_INFO;
+
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(client->view->rdclass, classbuf,
+ sizeof(classbuf));
+
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+ NS_LOGMODULE_UPDATE, level,
+ "update '%s/%s' denied due to allow-query",
+ namebuf, classbuf);
+ } else if (!update_possible) {
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(client->view->rdclass, classbuf,
+ sizeof(classbuf));
+
+ result = DNS_R_REFUSED;
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+ NS_LOGMODULE_UPDATE, ISC_LOG_INFO,
+ "update '%s/%s' denied", namebuf, classbuf);
+ }
+ return (result);
+}
+
+/*%
+ * Override the default acl logging when checking whether a client
+ * can update the zone or whether we can forward the request to the
+ * primary server based on IP address.
+ *
+ * 'message' contains the type of operation that is being attempted.
+ *
+ * 'secondary' indicates whether this is a secondary zone.
+ *
+ * If the zone has no access controls configured ('acl' == NULL &&
+ * 'has_ssutable == false`), log the attempt at info, otherwise at error.
+ * If 'secondary' is true, log at debug=3.
+ *
+ * If the request was signed, log that we received it.
+ */
+static isc_result_t
+checkupdateacl(ns_client_t *client, dns_acl_t *acl, const char *message,
+ dns_name_t *zonename, bool secondary, bool has_ssutable) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ int level = ISC_LOG_ERROR;
+ const char *msg = "denied";
+ isc_result_t result;
+
+ if (secondary && acl == NULL) {
+ result = DNS_R_NOTIMP;
+ level = ISC_LOG_DEBUG(3);
+ msg = "disabled";
+ } else {
+ result = ns_client_checkaclsilent(client, NULL, acl, false);
+ if (result == ISC_R_SUCCESS) {
+ level = ISC_LOG_DEBUG(3);
+ msg = "approved";
+ } else if (acl == NULL && !has_ssutable) {
+ level = ISC_LOG_INFO;
+ }
+ }
+
+ if (client->signer != NULL) {
+ dns_name_format(client->signer, namebuf, sizeof(namebuf));
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+ NS_LOGMODULE_UPDATE, ISC_LOG_INFO,
+ "signer \"%s\" %s", namebuf, msg);
+ }
+
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(client->view->rdclass, classbuf,
+ sizeof(classbuf));
+
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+ NS_LOGMODULE_UPDATE, level, "%s '%s/%s' %s", message,
+ namebuf, classbuf, msg);
+ return (result);
+}
+
+/*%
+ * 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);
+}
+
+/*%
+ * Perform the updates in 'updates' in version 'ver' of 'db' and log the
+ * update in 'diff'.
+ *
+ * Ensures:
+ * \li 'updates' is empty.
+ */
+static isc_result_t
+do_diff(dns_diff_t *updates, dns_db_t *db, dns_dbversion_t *ver,
+ dns_diff_t *diff) {
+ isc_result_t result;
+ while (!ISC_LIST_EMPTY(updates->tuples)) {
+ dns_difftuple_t *t = ISC_LIST_HEAD(updates->tuples);
+ ISC_LIST_UNLINK(updates->tuples, t, link);
+ CHECK(do_one_tuple(&t, db, ver, diff));
+ }
+ return (ISC_R_SUCCESS);
+
+failure:
+ dns_diff_clear(diff);
+ return (result);
+}
+
+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;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_dbversion_t *oldver = NULL;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+
+ /*
+ * Only set the clientinfo 'versionp' if the new version is
+ * different from the current version
+ */
+ dns_db_currentversion(db, &oldver);
+ dns_clientinfo_init(&ci, NULL, (ver != oldver) ? ver : NULL);
+ dns_db_closeversion(db, &oldver, false);
+
+ node = NULL;
+ result = dns_db_findnodeext(db, name, false, &cm, &ci, &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;
+ dns_clientinfomethods_t cm;
+ dns_clientinfo_t ci;
+ dns_dbversion_t *oldver = NULL;
+ dns_fixedname_t fixed;
+
+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
+
+ /*
+ * Only set the clientinfo 'versionp' if the new version is
+ * different from the current version
+ */
+ dns_db_currentversion(db, &oldver);
+ dns_clientinfo_init(&ci, NULL, (ver != oldver) ? ver : NULL);
+ dns_db_closeversion(db, &oldver, false);
+
+ 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_findnodeext(db, name, false, &cm, &ci, &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;
+ }
+
+ if (rr_action == add_rr_prepare_action) {
+ add_rr_prepare_ctx_t *ctx = rr_action_data;
+
+ ctx->oldname = dns_fixedname_initname(&fixed);
+ dns_name_copy(name, ctx->oldname);
+ dns_rdataset_getownercase(&rdataset, ctx->oldname);
+ }
+
+ 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);
+
+static isc_result_t
+count_action(void *data, rr_t *rr) {
+ unsigned int *ui = (unsigned int *)data;
+
+ UNUSED(rr);
+
+ (*ui)++;
+
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * 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;
+}
+
+/*%
+ * Helper function for cname_incompatible_rrset_exists.
+ */
+static isc_result_t
+cname_compatibility_action(void *data, dns_rdataset_t *rrset) {
+ UNUSED(data);
+ if (rrset->type != dns_rdatatype_cname &&
+ !dns_rdatatype_atcname(rrset->type))
+ {
+ return (ISC_R_EXISTS);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Check whether there is an rrset incompatible with adding a CNAME RR,
+ * i.e., anything but another CNAME (which can be replaced) or a
+ * DNSSEC RR (which can coexist).
+ *
+ * If such an incompatible rrset exists, set '*exists' to true.
+ * Otherwise, set it to false.
+ */
+static isc_result_t
+cname_incompatible_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, cname_compatibility_action, NULL);
+ RETURN_EXISTENCE_FLAG;
+}
+
+/*%
+ * Helper function for rr_count().
+ */
+static isc_result_t
+count_rr_action(void *data, rr_t *rr) {
+ int *countp = data;
+ UNUSED(rr);
+ (*countp)++;
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * Count the number of RRs of 'type' belonging to 'name' in 'ver' of 'db'.
+ */
+static isc_result_t
+rr_count(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_rdatatype_t type, dns_rdatatype_t covers, int *countp) {
+ *countp = 0;
+ return (foreach_rr(db, ver, name, type, covers, count_rr_action,
+ countp));
+}
+
+/*%
+ * 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;
+}
+
+/*
+ * 'ssu_check_t' is used to pass the arguments to
+ * dns_ssutable_checkrules() to the callback function
+ * ssu_checkrule().
+ */
+typedef struct {
+ /* The ownername of the record to be updated. */
+ dns_name_t *name;
+
+ /* The signature's name if the request was signed. */
+ dns_name_t *signer;
+
+ /* The address of the client. */
+ isc_netaddr_t *addr;
+
+ /* The ACL environment */
+ dns_aclenv_t *aclenv;
+
+ /* Whether the request was sent via TCP. */
+ bool tcp;
+
+ /* The ssu table to check against. */
+ dns_ssutable_t *table;
+
+ /* the key used for TKEY requests */
+ dst_key_t *key;
+} ssu_check_t;
+
+static isc_result_t
+ssu_checkrule(void *data, dns_rdataset_t *rrset) {
+ ssu_check_t *ssuinfo = data;
+ bool rule_ok = false;
+
+ /*
+ * If we're deleting all records, it's ok to delete RRSIG and NSEC even
+ * if we're normally not allowed to.
+ */
+ if (rrset->type == dns_rdatatype_rrsig ||
+ rrset->type == dns_rdatatype_nsec)
+ {
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * krb5-subdomain-self-rhs and ms-subdomain-self-rhs need
+ * to check the PTR and SRV target names so extract them
+ * from the resource records.
+ */
+ if (rrset->rdclass == dns_rdataclass_in &&
+ (rrset->type == dns_rdatatype_srv ||
+ rrset->type == dns_rdatatype_ptr))
+ {
+ dns_name_t *target = NULL;
+ dns_rdata_ptr_t ptr;
+ dns_rdata_in_srv_t srv;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_clone(rrset, &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 (rrset->type == dns_rdatatype_ptr) {
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &ptr.ptr;
+ }
+ if (rrset->type == dns_rdatatype_srv) {
+ result = dns_rdata_tostruct(&rdata, &srv, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &srv.target;
+ }
+ rule_ok = dns_ssutable_checkrules(
+ ssuinfo->table, ssuinfo->signer, ssuinfo->name,
+ ssuinfo->addr, ssuinfo->tcp, ssuinfo->aclenv,
+ rrset->type, target, ssuinfo->key, NULL);
+ if (!rule_ok) {
+ break;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ rule_ok = false;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ } else {
+ rule_ok = dns_ssutable_checkrules(
+ ssuinfo->table, ssuinfo->signer, ssuinfo->name,
+ ssuinfo->addr, ssuinfo->tcp, ssuinfo->aclenv,
+ rrset->type, NULL, ssuinfo->key, NULL);
+ }
+ return (rule_ok ? ISC_R_SUCCESS : ISC_R_FAILURE);
+}
+
+static bool
+ssu_checkall(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
+ dns_ssutable_t *ssutable, dns_name_t *signer, isc_netaddr_t *addr,
+ dns_aclenv_t *aclenv, bool tcp, dst_key_t *key) {
+ isc_result_t result;
+ ssu_check_t ssuinfo;
+
+ ssuinfo.name = name;
+ ssuinfo.table = ssutable;
+ ssuinfo.signer = signer;
+ ssuinfo.addr = addr;
+ ssuinfo.aclenv = aclenv;
+ ssuinfo.tcp = tcp;
+ ssuinfo.key = key;
+ result = foreach_rrset(db, ver, name, ssu_checkrule, &ssuinfo);
+ return (result == ISC_R_SUCCESS);
+}
+
+static isc_result_t
+ssu_checkrr(void *data, rr_t *rr) {
+ isc_result_t result;
+ ssu_check_t *ssuinfo = data;
+ dns_name_t *target = NULL;
+ dns_rdata_ptr_t ptr;
+ dns_rdata_in_srv_t srv;
+ bool answer;
+
+ if (rr->rdata.type == dns_rdatatype_ptr) {
+ result = dns_rdata_tostruct(&rr->rdata, &ptr, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &ptr.ptr;
+ }
+ if (rr->rdata.type == dns_rdatatype_srv) {
+ result = dns_rdata_tostruct(&rr->rdata, &srv, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &srv.target;
+ }
+
+ answer = dns_ssutable_checkrules(
+ ssuinfo->table, ssuinfo->signer, ssuinfo->name, ssuinfo->addr,
+ ssuinfo->tcp, ssuinfo->aclenv, rr->rdata.type, target,
+ ssuinfo->key, NULL);
+ return (answer ? ISC_R_SUCCESS : ISC_R_FAILURE);
+}
+
+/**************************************************************************/
+/*
+ * 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.
+ */
+
+/*%
+ * Append a tuple asserting the existence of the RR with
+ * 'name' and 'rdata' to 'diff'.
+ */
+static isc_result_t
+temp_append(dns_diff_t *diff, dns_name_t *name, dns_rdata_t *rdata) {
+ isc_result_t result;
+ dns_difftuple_t *tuple = NULL;
+
+ REQUIRE(DNS_DIFF_VALID(diff));
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_EXISTS, name, 0,
+ rdata, &tuple));
+ ISC_LIST_APPEND(diff->tuples, tuple, link);
+failure:
+ return (result);
+}
+
+/*%
+ * Compare two rdatasets represented as sorted lists of tuples.
+ * All list elements must have the same owner name and type.
+ * Return ISC_R_SUCCESS if the rdatasets are equal, rcode(dns_rcode_nxrrset)
+ * if not.
+ */
+static isc_result_t
+temp_check_rrset(dns_difftuple_t *a, dns_difftuple_t *b) {
+ for (;;) {
+ if (a == NULL || b == NULL) {
+ break;
+ }
+ INSIST(a->op == DNS_DIFFOP_EXISTS &&
+ b->op == DNS_DIFFOP_EXISTS);
+ INSIST(a->rdata.type == b->rdata.type);
+ INSIST(dns_name_equal(&a->name, &b->name));
+ if (dns_rdata_casecompare(&a->rdata, &b->rdata) != 0) {
+ return (DNS_R_NXRRSET);
+ }
+ a = ISC_LIST_NEXT(a, link);
+ b = ISC_LIST_NEXT(b, link);
+ }
+ if (a != NULL || b != NULL) {
+ return (DNS_R_NXRRSET);
+ }
+ return (ISC_R_SUCCESS);
+}
+
+/*%
+ * 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);
+}
+
+/*%
+ * Check the "RRset exists (value dependent)" prerequisite information
+ * in 'temp' against the contents of the database 'db'.
+ *
+ * Return ISC_R_SUCCESS if the prerequisites are satisfied,
+ * rcode(dns_rcode_nxrrset) if not.
+ *
+ * 'temp' must be pre-sorted.
+ */
+
+static isc_result_t
+temp_check(isc_mem_t *mctx, dns_diff_t *temp, dns_db_t *db,
+ dns_dbversion_t *ver, dns_name_t *tmpname, dns_rdatatype_t *typep) {
+ isc_result_t result;
+ dns_name_t *name;
+ dns_dbnode_t *node;
+ dns_difftuple_t *t;
+ dns_diff_t trash;
+
+ dns_diff_init(mctx, &trash);
+
+ /*
+ * For each name and type in the prerequisites,
+ * construct a sorted rdata list of the corresponding
+ * database contents, and compare the lists.
+ */
+ t = ISC_LIST_HEAD(temp->tuples);
+ while (t != NULL) {
+ name = &t->name;
+ dns_name_copy(name, tmpname);
+ *typep = t->rdata.type;
+
+ /* A new unique name begins here. */
+ node = NULL;
+ result = dns_db_findnode(db, name, false, &node);
+ if (result == ISC_R_NOTFOUND) {
+ dns_diff_clear(&trash);
+ return (DNS_R_NXRRSET);
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_diff_clear(&trash);
+ return (result);
+ }
+
+ /* A new unique type begins here. */
+ while (t != NULL && dns_name_equal(&t->name, name)) {
+ dns_rdatatype_t type, covers;
+ dns_rdataset_t rdataset;
+ dns_diff_t d_rrs; /* Database RRs with
+ * this name and type */
+ dns_diff_t u_rrs; /* Update RRs with
+ * this name and type */
+
+ *typep = type = t->rdata.type;
+ if (type == dns_rdatatype_rrsig ||
+ type == dns_rdatatype_sig)
+ {
+ covers = dns_rdata_covers(&t->rdata);
+ } else if (type == dns_rdatatype_any) {
+ dns_db_detachnode(db, &node);
+ dns_diff_clear(&trash);
+ return (DNS_R_NXRRSET);
+ } else {
+ covers = 0;
+ }
+
+ /*
+ * Collect all database RRs for this name and type
+ * onto d_rrs and sort them.
+ */
+ dns_rdataset_init(&rdataset);
+ result = dns_db_findrdataset(db, node, ver, type,
+ covers, (isc_stdtime_t)0,
+ &rdataset, NULL);
+ if (result != ISC_R_SUCCESS) {
+ dns_db_detachnode(db, &node);
+ dns_diff_clear(&trash);
+ return (DNS_R_NXRRSET);
+ }
+
+ dns_diff_init(mctx, &d_rrs);
+ dns_diff_init(mctx, &u_rrs);
+
+ 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 = temp_append(&d_rrs, name, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+ result = dns_diff_sort(&d_rrs, temp_order);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * Collect all update RRs for this name and type
+ * onto u_rrs. No need to sort them here -
+ * they are already sorted.
+ */
+ while (t != NULL && dns_name_equal(&t->name, name) &&
+ t->rdata.type == type)
+ {
+ dns_difftuple_t *next = ISC_LIST_NEXT(t, link);
+ ISC_LIST_UNLINK(temp->tuples, t, link);
+ ISC_LIST_APPEND(u_rrs.tuples, t, link);
+ t = next;
+ }
+
+ /* Compare the two sorted lists. */
+ result = temp_check_rrset(ISC_LIST_HEAD(u_rrs.tuples),
+ ISC_LIST_HEAD(d_rrs.tuples));
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+
+ /*
+ * We are done with the tuples, but we can't free
+ * them yet because "name" still points into one
+ * of them. Move them on a temporary list.
+ */
+ ISC_LIST_APPENDLIST(trash.tuples, u_rrs.tuples, link);
+ ISC_LIST_APPENDLIST(trash.tuples, d_rrs.tuples, link);
+ dns_rdataset_disassociate(&rdataset);
+
+ continue;
+
+ failure:
+ dns_diff_clear(&d_rrs);
+ dns_diff_clear(&u_rrs);
+ dns_diff_clear(&trash);
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ return (result);
+ }
+
+ dns_db_detachnode(db, &node);
+ }
+
+ dns_diff_clear(&trash);
+ return (ISC_R_SUCCESS);
+}
+
+/**************************************************************************/
+/*
+ * 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 iff 'db_rr' is neither a SOA nor an NS RR nor
+ * an RRSIG nor an NSEC3PARAM nor a NSEC.
+ */
+static bool
+type_not_soa_nor_ns_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ UNUSED(update_rr);
+ return ((db_rr->type != dns_rdatatype_soa &&
+ db_rr->type != dns_rdatatype_ns &&
+ db_rr->type != dns_rdatatype_nsec3param &&
+ db_rr->type != dns_rdatatype_rrsig &&
+ db_rr->type != dns_rdatatype_nsec)
+ ? true
+ : false);
+}
+
+/*%
+ * Return true iff 'db_rr' is neither a RRSIG nor a NSEC.
+ */
+static bool
+type_not_dnssec(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ UNUSED(update_rr);
+ return ((db_rr->type != dns_rdatatype_rrsig &&
+ db_rr->type != dns_rdatatype_nsec)
+ ? true
+ : false);
+}
+
+/*%
+ * 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 iff the two RRs have identical rdata.
+ */
+static bool
+rr_equal_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ /*
+ * XXXRTH This is not a problem, but we should consider creating
+ * dns_rdata_equal() (that used dns_name_equal()), since it
+ * would be faster. Not a priority.
+ */
+ return (dns_rdata_casecompare(update_rr, db_rr) == 0 ? true : false);
+}
+
+/*%
+ * Return true iff 'update_rr' should replace 'db_rr' according
+ * to the special RFC2136 rules for CNAME, SOA, and WKS records.
+ *
+ * RFC2136 does not mention NSEC or DNAME, but multiple NSECs or DNAMEs
+ * make little sense, so we replace those, too.
+ *
+ * Additionally replace RRSIG that have been generated by the same key
+ * for the same type. This simplifies refreshing a offline KSK by not
+ * requiring that the old RRSIG be deleted. It also simplifies key
+ * rollover by only requiring that the new RRSIG be added.
+ */
+static bool
+replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
+ dns_rdata_rrsig_t updatesig, dbsig;
+ isc_result_t result;
+
+ if (db_rr->type != update_rr->type) {
+ return (false);
+ }
+ if (db_rr->type == dns_rdatatype_cname) {
+ return (true);
+ }
+ if (db_rr->type == dns_rdatatype_dname) {
+ return (true);
+ }
+ if (db_rr->type == dns_rdatatype_soa) {
+ return (true);
+ }
+ if (db_rr->type == dns_rdatatype_nsec) {
+ return (true);
+ }
+ if (db_rr->type == dns_rdatatype_rrsig) {
+ /*
+ * Replace existing RRSIG with the same keyid,
+ * covered and algorithm.
+ */
+ result = dns_rdata_tostruct(db_rr, &dbsig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ result = dns_rdata_tostruct(update_rr, &updatesig, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ if (dbsig.keyid == updatesig.keyid &&
+ dbsig.covered == updatesig.covered &&
+ dbsig.algorithm == updatesig.algorithm)
+ {
+ return (true);
+ }
+ }
+ if (db_rr->type == dns_rdatatype_wks) {
+ /*
+ * Compare the address and protocol fields only. These
+ * form the first five bytes of the RR data. Do a
+ * raw binary comparison; unpacking the WKS RRs using
+ * dns_rdata_tostruct() might be cleaner in some ways.
+ */
+ INSIST(db_rr->length >= 5 && update_rr->length >= 5);
+ return (memcmp(db_rr->data, update_rr->data, 5) == 0 ? true
+ : false);
+ }
+
+ if (db_rr->type == dns_rdatatype_nsec3param) {
+ if (db_rr->length != update_rr->length) {
+ return (false);
+ }
+ INSIST(db_rr->length >= 4 && update_rr->length >= 4);
+ /*
+ * Replace NSEC3PARAM records that only differ by the
+ * flags field.
+ */
+ if (db_rr->data[0] == update_rr->data[0] &&
+ memcmp(db_rr->data + 2, update_rr->data + 2,
+ update_rr->length - 2) == 0)
+ {
+ return (true);
+ }
+ }
+ return (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));
+}
+
+/**************************************************************************/
+
+static isc_result_t
+add_rr_prepare_action(void *data, rr_t *rr) {
+ isc_result_t result = ISC_R_SUCCESS;
+ add_rr_prepare_ctx_t *ctx = data;
+ dns_difftuple_t *tuple = NULL;
+ bool equal, case_equal, ttl_equal;
+
+ /*
+ * Are the new and old cases equal?
+ */
+ case_equal = dns_name_caseequal(ctx->name, ctx->oldname);
+
+ /*
+ * Are the ttl's equal?
+ */
+ ttl_equal = rr->ttl == ctx->update_rr_ttl;
+
+ /*
+ * If the update RR is a "duplicate" of a existing RR,
+ * the update should be silently ignored.
+ */
+ equal = (dns_rdata_casecompare(&rr->rdata, ctx->update_rr) == 0);
+ if (equal && case_equal && ttl_equal) {
+ ctx->ignore_add = true;
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If this RR is "equal" to the update RR, it should
+ * be deleted before the update RR is added.
+ */
+ if (replaces_p(ctx->update_rr, &rr->rdata)) {
+ CHECK(dns_difftuple_create(ctx->del_diff.mctx, DNS_DIFFOP_DEL,
+ ctx->oldname, rr->ttl, &rr->rdata,
+ &tuple));
+ dns_diff_append(&ctx->del_diff, &tuple);
+ return (ISC_R_SUCCESS);
+ }
+
+ /*
+ * If this RR differs in TTL or case from the update RR,
+ * its TTL and case must be adjusted.
+ */
+ if (!ttl_equal || !case_equal) {
+ CHECK(dns_difftuple_create(ctx->del_diff.mctx, DNS_DIFFOP_DEL,
+ ctx->oldname, rr->ttl, &rr->rdata,
+ &tuple));
+ dns_diff_append(&ctx->del_diff, &tuple);
+ if (!equal) {
+ CHECK(dns_difftuple_create(
+ ctx->add_diff.mctx, DNS_DIFFOP_ADD, ctx->name,
+ ctx->update_rr_ttl, &rr->rdata, &tuple));
+ dns_diff_append(&ctx->add_diff, &tuple);
+ }
+ }
+failure:
+ return (result);
+}
+
+/**************************************************************************/
+/*
+ * Miscellaneous subroutines.
+ */
+
+/*%
+ * Extract a single update RR from 'section' of dynamic update message
+ * 'msg', with consistency checking.
+ *
+ * Stores the owner name, rdata, and TTL of the update RR at 'name',
+ * 'rdata', and 'ttl', respectively.
+ */
+static void
+get_current_rr(dns_message_t *msg, dns_section_t section,
+ dns_rdataclass_t zoneclass, dns_name_t **name,
+ dns_rdata_t *rdata, dns_rdatatype_t *covers, dns_ttl_t *ttl,
+ dns_rdataclass_t *update_class) {
+ dns_rdataset_t *rdataset;
+ isc_result_t result;
+ dns_message_currentname(msg, section, name);
+ rdataset = ISC_LIST_HEAD((*name)->list);
+ INSIST(rdataset != NULL);
+ INSIST(ISC_LIST_NEXT(rdataset, link) == NULL);
+ *covers = rdataset->covers;
+ *ttl = rdataset->ttl;
+ result = dns_rdataset_first(rdataset);
+ INSIST(result == ISC_R_SUCCESS);
+ dns_rdataset_current(rdataset, rdata);
+ INSIST(dns_rdataset_next(rdataset) == ISC_R_NOMORE);
+ *update_class = rdata->rdclass;
+ rdata->rdclass = zoneclass;
+}
+
+/*%
+ * Increment the SOA serial number of database 'db', version 'ver'.
+ * Replace the SOA record in the database, and log the
+ * change in 'diff'.
+ */
+
+/*
+ * XXXRTH Failures in this routine will be worth logging, when
+ * we have a logging system. Failure to find the zonename
+ * or the SOA rdataset warrant at least an UNEXPECTED_ERROR().
+ */
+
+static isc_result_t
+update_soa_serial(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;
+
+ 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, NULL);
+ 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);
+}
+
+/*%
+ * Check that the new SOA record at 'update_rdata' does not
+ * illegally cause the SOA serial number to decrease or stay
+ * unchanged relative to the existing SOA in 'db'.
+ *
+ * Sets '*ok' to true if the update is legal, false if not.
+ *
+ * William King points out that RFC2136 is inconsistent about
+ * the case where the serial number stays unchanged:
+ *
+ * section 3.4.2.2 requires a server to ignore a SOA update request
+ * if the serial number on the update SOA is less_than_or_equal to
+ * the zone SOA serial.
+ *
+ * section 3.6 requires a server to ignore a SOA update request if
+ * the serial is less_than the zone SOA serial.
+ *
+ * Paul says 3.4.2.2 is correct.
+ *
+ */
+static isc_result_t
+check_soa_increment(dns_db_t *db, dns_dbversion_t *ver,
+ dns_rdata_t *update_rdata, bool *ok) {
+ uint32_t db_serial;
+ uint32_t update_serial;
+ isc_result_t result;
+
+ update_serial = dns_soa_getserial(update_rdata);
+
+ result = dns_db_getsoaserial(db, ver, &db_serial);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ if (DNS_SERIAL_GE(db_serial, update_serial)) {
+ *ok = false;
+ } else {
+ *ok = true;
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+/**************************************************************************/
+/*%
+ * The actual update code in all its glory. We try to follow
+ * the RFC2136 pseudocode as closely as possible.
+ */
+
+static isc_result_t
+send_update_event(ns_client_t *client, dns_zone_t *zone) {
+ isc_result_t result = ISC_R_SUCCESS;
+ update_event_t *event = NULL;
+ isc_task_t *zonetask = NULL;
+ dns_ssutable_t *ssutable = NULL;
+ dns_message_t *request = client->message;
+ isc_mem_t *mctx = client->manager->mctx;
+ dns_aclenv_t *env = client->manager->aclenv;
+ dns_rdataclass_t zoneclass;
+ dns_rdatatype_t covers;
+ dns_name_t *zonename = NULL;
+ const dns_ssurule_t **rules = NULL;
+ size_t rule = 0, ruleslen = 0;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *ver = NULL;
+
+ CHECK(dns_zone_getdb(zone, &db));
+ zonename = dns_db_origin(db);
+ zoneclass = dns_db_class(db);
+ dns_zone_getssutable(zone, &ssutable);
+ dns_db_currentversion(db, &ver);
+
+ /*
+ * Update message processing can leak record existence information
+ * so check that we are allowed to query this zone. Additionally,
+ * if we would refuse all updates for this zone, we bail out here.
+ */
+ CHECK(checkqueryacl(client, dns_zone_getqueryacl(zone),
+ dns_zone_getorigin(zone),
+ dns_zone_getupdateacl(zone), ssutable));
+
+ /*
+ * Check requestor's permissions.
+ */
+ if (ssutable == NULL) {
+ CHECK(checkupdateacl(client, dns_zone_getupdateacl(zone),
+ "update", dns_zone_getorigin(zone), false,
+ false));
+ } else if (client->signer == NULL && !TCPCLIENT(client)) {
+ CHECK(checkupdateacl(client, NULL, "update",
+ dns_zone_getorigin(zone), false, true));
+ }
+
+ if (dns_zone_getupdatedisabled(zone)) {
+ FAILC(DNS_R_REFUSED, "dynamic update temporarily disabled "
+ "because the zone is frozen. Use "
+ "'rndc thaw' to re-enable updates.");
+ }
+
+ /*
+ * Prescan the update section, checking for updates that
+ * are illegal or violate policy.
+ */
+ if (ssutable != NULL) {
+ ruleslen = request->counts[DNS_SECTION_UPDATE];
+ rules = isc_mem_get(mctx, sizeof(*rules) * ruleslen);
+ memset(rules, 0, sizeof(*rules) * ruleslen);
+ }
+
+ for (rule = 0,
+ result = dns_message_firstname(request, DNS_SECTION_UPDATE);
+ result == ISC_R_SUCCESS;
+ rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE))
+ {
+ dns_name_t *name = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_ttl_t ttl;
+ dns_rdataclass_t update_class;
+
+ INSIST(ssutable == NULL || rule < ruleslen);
+
+ get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
+ &rdata, &covers, &ttl, &update_class);
+
+ if (!dns_name_issubdomain(name, zonename)) {
+ FAILC(DNS_R_NOTZONE, "update RR is outside zone");
+ }
+ if (update_class == zoneclass) {
+ /*
+ * Check for meta-RRs. The RFC2136 pseudocode says
+ * check for ANY|AXFR|MAILA|MAILB, but the text adds
+ * "or any other QUERY metatype"
+ */
+ if (dns_rdatatype_ismeta(rdata.type)) {
+ FAILC(DNS_R_FORMERR, "meta-RR in update");
+ }
+ result = dns_zone_checknames(zone, name, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ FAIL(DNS_R_REFUSED);
+ }
+ } else if (update_class == dns_rdataclass_any) {
+ if (ttl != 0 || rdata.length != 0 ||
+ (dns_rdatatype_ismeta(rdata.type) &&
+ rdata.type != dns_rdatatype_any))
+ {
+ FAILC(DNS_R_FORMERR, "meta-RR in update");
+ }
+ } else if (update_class == dns_rdataclass_none) {
+ if (ttl != 0 || dns_rdatatype_ismeta(rdata.type)) {
+ FAILC(DNS_R_FORMERR, "meta-RR in update");
+ }
+ } else {
+ update_log(client, zone, ISC_LOG_WARNING,
+ "update RR has incorrect class %d",
+ update_class);
+ FAIL(DNS_R_FORMERR);
+ }
+
+ /*
+ * draft-ietf-dnsind-simple-secure-update-01 says
+ * "Unlike traditional dynamic update, the client
+ * is forbidden from updating NSEC records."
+ */
+ if (rdata.type == dns_rdatatype_nsec3) {
+ FAILC(DNS_R_REFUSED, "explicit NSEC3 updates are not "
+ "allowed "
+ "in secure zones");
+ } else if (rdata.type == dns_rdatatype_nsec) {
+ FAILC(DNS_R_REFUSED, "explicit NSEC updates are not "
+ "allowed "
+ "in secure zones");
+ } else if (rdata.type == dns_rdatatype_rrsig &&
+ !dns_name_equal(name, zonename))
+ {
+ FAILC(DNS_R_REFUSED, "explicit RRSIG updates are "
+ "currently "
+ "not supported in secure zones "
+ "except "
+ "at the apex");
+ }
+
+ if (ssutable != NULL) {
+ isc_netaddr_t netaddr;
+ dns_name_t *target = NULL;
+ dst_key_t *tsigkey = NULL;
+ dns_rdata_ptr_t ptr;
+ dns_rdata_in_srv_t srv;
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+
+ if (client->message->tsigkey != NULL) {
+ tsigkey = client->message->tsigkey->key;
+ }
+
+ if ((update_class == dns_rdataclass_in ||
+ update_class == dns_rdataclass_none) &&
+ rdata.type == dns_rdatatype_ptr)
+ {
+ result = dns_rdata_tostruct(&rdata, &ptr, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &ptr.ptr;
+ }
+
+ if ((update_class == dns_rdataclass_in ||
+ update_class == dns_rdataclass_none) &&
+ rdata.type == dns_rdatatype_srv)
+ {
+ result = dns_rdata_tostruct(&rdata, &srv, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ target = &srv.target;
+ }
+
+ if (update_class == dns_rdataclass_any &&
+ zoneclass == dns_rdataclass_in &&
+ (rdata.type == dns_rdatatype_ptr ||
+ rdata.type == dns_rdatatype_srv))
+ {
+ ssu_check_t ssuinfo;
+
+ ssuinfo.name = name;
+ ssuinfo.table = ssutable;
+ ssuinfo.signer = client->signer;
+ ssuinfo.addr = &netaddr;
+ ssuinfo.aclenv = env;
+ ssuinfo.tcp = TCPCLIENT(client);
+ ssuinfo.key = tsigkey;
+
+ result = foreach_rr(db, ver, name, rdata.type,
+ dns_rdatatype_none,
+ ssu_checkrr, &ssuinfo);
+ if (result != ISC_R_SUCCESS) {
+ FAILC(DNS_R_REFUSED,
+ "rejected by secure update");
+ }
+ } else if (target != NULL &&
+ update_class == dns_rdataclass_none)
+ {
+ bool flag;
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+ if (flag &&
+ !dns_ssutable_checkrules(
+ ssutable, client->signer, name,
+ &netaddr, TCPCLIENT(client), env,
+ rdata.type, target, tsigkey,
+ &rules[rule]))
+ {
+ FAILC(DNS_R_REFUSED,
+ "rejected by secure update");
+ }
+ } else if (rdata.type != dns_rdatatype_any) {
+ if (!dns_ssutable_checkrules(
+ ssutable, client->signer, name,
+ &netaddr, TCPCLIENT(client), env,
+ rdata.type, target, tsigkey,
+ &rules[rule]))
+ {
+ FAILC(DNS_R_REFUSED, "rejected by "
+ "secure update");
+ }
+ } else {
+ if (!ssu_checkall(db, ver, name, ssutable,
+ client->signer, &netaddr, env,
+ TCPCLIENT(client), tsigkey))
+ {
+ FAILC(DNS_R_REFUSED, "rejected by "
+ "secure update");
+ }
+ }
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ FAIL(result);
+ }
+
+ update_log(client, zone, LOGLEVEL_DEBUG, "update section prescan OK");
+
+ result = isc_quota_attach(&client->manager->sctx->updquota,
+ &(isc_quota_t *){ NULL });
+ if (result != ISC_R_SUCCESS) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "update failed: too many DNS UPDATEs queued (%s)",
+ isc_result_totext(result));
+ ns_stats_increment(client->manager->sctx->nsstats,
+ ns_statscounter_updatequota);
+ CHECK(DNS_R_DROP);
+ }
+
+ event = (update_event_t *)isc_event_allocate(
+ client->mctx, client, DNS_EVENT_UPDATE, update_action, NULL,
+ sizeof(*event));
+ event->zone = zone;
+ event->result = ISC_R_SUCCESS;
+ event->rules = rules;
+ event->ruleslen = ruleslen;
+ rules = NULL;
+
+ INSIST(client->nupdates == 0);
+ client->nupdates++;
+ event->ev_arg = client;
+
+ isc_nmhandle_attach(client->handle, &client->updatehandle);
+ dns_zone_gettask(zone, &zonetask);
+ isc_task_send(zonetask, ISC_EVENT_PTR(&event));
+
+failure:
+ if (db != NULL) {
+ dns_db_closeversion(db, &ver, false);
+ dns_db_detach(&db);
+ }
+
+ if (rules != NULL) {
+ isc_mem_put(mctx, rules, sizeof(*rules) * ruleslen);
+ }
+
+ if (ssutable != NULL) {
+ dns_ssutable_detach(&ssutable);
+ }
+
+ return (result);
+}
+
+static void
+respond(ns_client_t *client, isc_result_t result) {
+ isc_result_t msg_result;
+
+ msg_result = dns_message_reply(client->message, true);
+ if (msg_result != ISC_R_SUCCESS) {
+ isc_log_write(ns_lctx, NS_LOGCATEGORY_UPDATE,
+ NS_LOGMODULE_UPDATE, ISC_LOG_ERROR,
+ "could not create update response message: %s",
+ isc_result_totext(msg_result));
+ ns_client_drop(client, msg_result);
+ isc_nmhandle_detach(&client->reqhandle);
+ return;
+ }
+
+ client->message->rcode = dns_result_torcode(result);
+ ns_client_send(client);
+ isc_nmhandle_detach(&client->reqhandle);
+}
+
+void
+ns_update_start(ns_client_t *client, isc_nmhandle_t *handle,
+ isc_result_t sigresult) {
+ dns_message_t *request = client->message;
+ isc_result_t result;
+ dns_name_t *zonename;
+ dns_rdataset_t *zone_rdataset;
+ dns_zone_t *zone = NULL, *raw = NULL;
+
+ /*
+ * Attach to the request handle. This will be held until
+ * we respond, or drop the request.
+ */
+ isc_nmhandle_attach(handle, &client->reqhandle);
+
+ /*
+ * Interpret the zone section.
+ */
+ result = dns_message_firstname(request, DNS_SECTION_ZONE);
+ if (result != ISC_R_SUCCESS) {
+ FAILC(DNS_R_FORMERR, "update zone section empty");
+ }
+
+ /*
+ * The zone section must contain exactly one "question", and
+ * it must be of type SOA.
+ */
+ zonename = NULL;
+ dns_message_currentname(request, DNS_SECTION_ZONE, &zonename);
+ zone_rdataset = ISC_LIST_HEAD(zonename->list);
+ if (zone_rdataset->type != dns_rdatatype_soa) {
+ FAILC(DNS_R_FORMERR, "update zone section contains non-SOA");
+ }
+ if (ISC_LIST_NEXT(zone_rdataset, link) != NULL) {
+ FAILC(DNS_R_FORMERR, "update zone section contains multiple "
+ "RRs");
+ }
+
+ /* The zone section must have exactly one name. */
+ result = dns_message_nextname(request, DNS_SECTION_ZONE);
+ if (result != ISC_R_NOMORE) {
+ FAILC(DNS_R_FORMERR, "update zone section contains multiple "
+ "RRs");
+ }
+
+ result = dns_zt_find(client->view->zonetable, zonename, 0, NULL, &zone);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * If we found a zone that is a parent of the update zonename,
+ * detach it so it isn't mentioned in log - it is irrelevant.
+ */
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ FAILN(DNS_R_NOTAUTH, zonename,
+ "not authoritative for update zone");
+ }
+
+ /*
+ * If there is a raw (unsigned) zone associated with this
+ * zone then it processes the UPDATE request.
+ */
+ dns_zone_getraw(zone, &raw);
+ if (raw != NULL) {
+ dns_zone_detach(&zone);
+ dns_zone_attach(raw, &zone);
+ dns_zone_detach(&raw);
+ }
+
+ switch (dns_zone_gettype(zone)) {
+ case dns_zone_primary:
+ case dns_zone_dlz:
+ /*
+ * We can now fail due to a bad signature as we now know
+ * that we are the primary.
+ */
+ if (sigresult != ISC_R_SUCCESS) {
+ FAIL(sigresult);
+ }
+ dns_message_clonebuffer(client->message);
+ CHECK(send_update_event(client, zone));
+ break;
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ dns_message_clonebuffer(client->message);
+ CHECK(send_forward_event(client, zone));
+ break;
+ default:
+ FAILC(DNS_R_NOTAUTH, "not authoritative for update zone");
+ }
+ return;
+
+failure:
+ if (result == DNS_R_REFUSED) {
+ inc_stats(client, zone, ns_statscounter_updaterej);
+ }
+
+ /*
+ * We failed without having sent an update event to the zone.
+ * We are still in the client task context, so we can
+ * simply give an error response without switching tasks.
+ */
+ if (result == DNS_R_DROP) {
+ ns_client_drop(client, result);
+ isc_nmhandle_detach(&client->reqhandle);
+ } else {
+ respond(client, result);
+ }
+
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+}
+
+/*%
+ * DS records are not allowed to exist without corresponding NS records,
+ * RFC 3658, 2.2 Protocol Change,
+ * "DS RRsets MUST NOT appear at non-delegation points or at a zone's apex".
+ */
+
+static isc_result_t
+remove_orphaned_ds(dns_db_t *db, dns_dbversion_t *newver, dns_diff_t *diff) {
+ isc_result_t result;
+ bool ns_exists;
+ dns_difftuple_t *tuple;
+ dns_diff_t temp_diff;
+
+ dns_diff_init(diff->mctx, &temp_diff);
+
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ if (!((tuple->op == DNS_DIFFOP_DEL &&
+ tuple->rdata.type == dns_rdatatype_ns) ||
+ (tuple->op == DNS_DIFFOP_ADD &&
+ tuple->rdata.type == dns_rdatatype_ds)))
+ {
+ continue;
+ }
+ CHECK(rrset_exists(db, newver, &tuple->name, dns_rdatatype_ns,
+ 0, &ns_exists));
+ if (ns_exists &&
+ !dns_name_equal(&tuple->name, dns_db_origin(db)))
+ {
+ continue;
+ }
+ CHECK(delete_if(true_p, db, newver, &tuple->name,
+ dns_rdatatype_ds, 0, NULL, &temp_diff));
+ }
+ result = ISC_R_SUCCESS;
+
+failure:
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = ISC_LIST_HEAD(temp_diff.tuples))
+ {
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ dns_diff_appendminimal(diff, &tuple);
+ }
+ return (result);
+}
+
+/*
+ * This implements the post load integrity checks for mx records.
+ */
+static isc_result_t
+check_mx(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *newver, dns_diff_t *diff) {
+ char tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:123.123.123.123.")];
+ char ownerbuf[DNS_NAME_FORMATSIZE];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char altbuf[DNS_NAME_FORMATSIZE];
+ dns_difftuple_t *t;
+ dns_fixedname_t fixed;
+ dns_name_t *foundname;
+ dns_rdata_mx_t mx;
+ dns_rdata_t rdata;
+ bool ok = true;
+ bool isaddress;
+ isc_result_t result;
+ struct in6_addr addr6;
+ struct in_addr addr;
+ dns_zoneopt_t options;
+
+ foundname = dns_fixedname_initname(&fixed);
+ dns_rdata_init(&rdata);
+ options = dns_zone_getoptions(zone);
+
+ for (t = ISC_LIST_HEAD(diff->tuples); t != NULL;
+ t = ISC_LIST_NEXT(t, link))
+ {
+ if (t->op != DNS_DIFFOP_ADD ||
+ t->rdata.type != dns_rdatatype_mx)
+ {
+ continue;
+ }
+
+ result = dns_rdata_tostruct(&t->rdata, &mx, NULL);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ /*
+ * Check if we will error out if we attempt to reload the
+ * zone.
+ */
+ dns_name_format(&mx.mx, namebuf, sizeof(namebuf));
+ dns_name_format(&t->name, ownerbuf, sizeof(ownerbuf));
+ isaddress = false;
+ if ((options & DNS_ZONEOPT_CHECKMX) != 0 &&
+ strlcpy(tmp, namebuf, sizeof(tmp)) < sizeof(tmp))
+ {
+ 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)
+ {
+ isaddress = true;
+ }
+ }
+
+ if (isaddress && (options & DNS_ZONEOPT_CHECKMXFAIL) != 0) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "%s/MX: '%s': %s", ownerbuf, namebuf,
+ isc_result_totext(DNS_R_MXISADDRESS));
+ ok = false;
+ } else if (isaddress) {
+ update_log(client, zone, ISC_LOG_WARNING,
+ "%s/MX: warning: '%s': %s", ownerbuf,
+ namebuf,
+ isc_result_totext(DNS_R_MXISADDRESS));
+ }
+
+ /*
+ * Check zone integrity checks.
+ */
+ if ((options & DNS_ZONEOPT_CHECKINTEGRITY) == 0) {
+ continue;
+ }
+ result = dns_db_find(db, &mx.mx, newver, dns_rdatatype_a, 0, 0,
+ NULL, foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ continue;
+ }
+
+ if (result == DNS_R_NXRRSET) {
+ result = dns_db_find(db, &mx.mx, newver,
+ dns_rdatatype_aaaa, 0, 0, NULL,
+ foundname, NULL, NULL);
+ if (result == ISC_R_SUCCESS) {
+ continue;
+ }
+ }
+
+ if (result == DNS_R_NXRRSET || result == DNS_R_NXDOMAIN) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "%s/MX '%s' has no address records "
+ "(A or AAAA)",
+ ownerbuf, namebuf);
+ ok = false;
+ } else if (result == DNS_R_CNAME) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "%s/MX '%s' is a CNAME (illegal)", ownerbuf,
+ namebuf);
+ ok = false;
+ } else if (result == DNS_R_DNAME) {
+ dns_name_format(foundname, altbuf, sizeof altbuf);
+ update_log(client, zone, ISC_LOG_ERROR,
+ "%s/MX '%s' is below a DNAME '%s' (illegal)",
+ ownerbuf, namebuf, altbuf);
+ ok = false;
+ }
+ }
+ return (ok ? ISC_R_SUCCESS : DNS_R_REFUSED);
+}
+
+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) {
+ result = dns_db_findnsec3node(db, name, false, &node);
+ } else {
+ result = dns_db_findnode(db, name, false, &node);
+ }
+ if (result == ISC_R_NOTFOUND) {
+ *flag = false;
+ result = ISC_R_SUCCESS;
+ goto failure;
+ } else {
+ CHECK(result);
+ }
+ 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);
+}
+
+static isc_result_t
+get_iterations(dns_db_t *db, dns_dbversion_t *ver, dns_rdatatype_t privatetype,
+ unsigned int *iterationsp) {
+ dns_dbnode_t *node = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ dns_rdataset_t rdataset;
+ isc_result_t result;
+ unsigned int iterations = 0;
+
+ dns_rdataset_init(&rdataset);
+
+ result = dns_db_getoriginnode(db, &node);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ 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 rdata = DNS_RDATA_INIT;
+ dns_rdataset_current(&rdataset, &rdata);
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+ if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ continue;
+ }
+ if (nsec3param.iterations > iterations) {
+ iterations = nsec3param.iterations;
+ }
+ }
+ 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))
+ {
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+ dns_rdata_t private = DNS_RDATA_INIT;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+
+ dns_rdataset_current(&rdataset, &rdata);
+ if (!dns_nsec3param_fromprivate(&private, &rdata, buf,
+ sizeof(buf)))
+ {
+ continue;
+ }
+ CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+ if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0) {
+ continue;
+ }
+ if (nsec3param.iterations > iterations) {
+ iterations = nsec3param.iterations;
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ goto failure;
+ }
+
+success:
+ *iterationsp = iterations;
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (node != NULL) {
+ dns_db_detachnode(db, &node);
+ }
+ if (dns_rdataset_isassociated(&rdataset)) {
+ dns_rdataset_disassociate(&rdataset);
+ }
+ return (result);
+}
+
+/*
+ * Prevent the zone entering a inconsistent state where
+ * NSEC only DNSKEYs are present with NSEC3 chains.
+ */
+static isc_result_t
+check_dnssec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_diff_t *diff) {
+ isc_result_t result;
+ unsigned int iterations = 0;
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+
+ /* Refuse to allow NSEC3 with NSEC-only keys */
+ if (!dns_zone_check_dnskey_nsec3(zone, db, ver, diff, NULL, 0)) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "NSEC only DNSKEYs and NSEC3 chains not allowed");
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+
+ /* Verify NSEC3 params */
+ CHECK(get_iterations(db, ver, privatetype, &iterations));
+ if (iterations > dns_nsec3_maxiterations()) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "too many NSEC3 iterations (%u)", iterations);
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+
+failure:
+ return (result);
+}
+
+/*
+ * Delay NSEC3PARAM changes as they need to be applied to the whole zone.
+ */
+static isc_result_t
+add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, dns_diff_t *diff) {
+ isc_result_t result = ISC_R_SUCCESS;
+ dns_difftuple_t *tuple, *newtuple = NULL, *next;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE + 1];
+ dns_diff_t temp_diff;
+ dns_diffop_t op;
+ bool flag;
+ dns_name_t *name = dns_zone_getorigin(zone);
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+ uint32_t ttl = 0;
+ bool ttl_good = false;
+
+ update_log(client, zone, ISC_LOG_DEBUG(3),
+ "checking for NSEC3PARAM changes");
+
+ dns_diff_init(diff->mctx, &temp_diff);
+
+ /*
+ * Extract NSEC3PARAM tuples from list.
+ */
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) {
+ next = ISC_LIST_NEXT(tuple, link);
+
+ if (tuple->rdata.type != dns_rdatatype_nsec3param ||
+ !dns_name_equal(name, &tuple->name))
+ {
+ continue;
+ }
+ ISC_LIST_UNLINK(diff->tuples, tuple, link);
+ ISC_LIST_APPEND(temp_diff.tuples, tuple, link);
+ }
+
+ /*
+ * Extract TTL changes pairs, we don't need to convert these to
+ * delayed changes.
+ */
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = next)
+ {
+ if (tuple->op == DNS_DIFFOP_ADD) {
+ if (!ttl_good) {
+ /*
+ * Any adds here will contain the final
+ * NSEC3PARAM RRset TTL.
+ */
+ ttl = tuple->ttl;
+ ttl_good = true;
+ }
+ /*
+ * Walk the temp_diff list looking for the
+ * corresponding delete.
+ */
+ next = ISC_LIST_HEAD(temp_diff.tuples);
+ while (next != NULL) {
+ unsigned char *next_data = next->rdata.data;
+ unsigned char *tuple_data = tuple->rdata.data;
+ if (next->op == DNS_DIFFOP_DEL &&
+ next->rdata.length == tuple->rdata.length &&
+ !memcmp(next_data, tuple_data,
+ next->rdata.length))
+ {
+ ISC_LIST_UNLINK(temp_diff.tuples, next,
+ link);
+ ISC_LIST_APPEND(diff->tuples, next,
+ link);
+ break;
+ }
+ next = ISC_LIST_NEXT(next, link);
+ }
+ /*
+ * If we have not found a pair move onto the next
+ * tuple.
+ */
+ if (next == NULL) {
+ next = ISC_LIST_NEXT(tuple, link);
+ continue;
+ }
+ /*
+ * Find the next tuple to be processed before
+ * unlinking then complete moving the pair to 'diff'.
+ */
+ next = ISC_LIST_NEXT(tuple, link);
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ ISC_LIST_APPEND(diff->tuples, tuple, link);
+ } else {
+ next = ISC_LIST_NEXT(tuple, link);
+ }
+ }
+
+ /*
+ * Preserve any ongoing changes from a BIND 9.6.x upgrade.
+ *
+ * Any NSEC3PARAM records with flags other than OPTOUT named
+ * in managing and should not be touched so revert such changes
+ * taking into account any TTL change of the NSEC3PARAM RRset.
+ */
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = next)
+ {
+ next = ISC_LIST_NEXT(tuple, link);
+ if ((tuple->rdata.data[1] & ~DNS_NSEC3FLAG_OPTOUT) != 0) {
+ /*
+ * If we haven't had any adds then the tuple->ttl must
+ * be the original ttl and should be used for any
+ * future changes.
+ */
+ if (!ttl_good) {
+ ttl = tuple->ttl;
+ ttl_good = true;
+ }
+ op = (tuple->op == DNS_DIFFOP_DEL) ? DNS_DIFFOP_ADD
+ : DNS_DIFFOP_DEL;
+ CHECK(dns_difftuple_create(diff->mctx, op, name, ttl,
+ &tuple->rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ dns_diff_appendminimal(diff, &tuple);
+ }
+ }
+
+ /*
+ * We now have just the actual changes to the NSEC3PARAM RRset.
+ * Convert the adds to delayed adds and the deletions into delayed
+ * deletions.
+ */
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = next)
+ {
+ /*
+ * If we haven't had any adds then the tuple->ttl must be the
+ * original ttl and should be used for any future changes.
+ */
+ if (!ttl_good) {
+ ttl = tuple->ttl;
+ ttl_good = true;
+ }
+ if (tuple->op == DNS_DIFFOP_ADD) {
+ bool nseconly = false;
+
+ /*
+ * Look for any deletes which match this ADD ignoring
+ * flags. We don't need to explicitly remove them as
+ * they will be removed a side effect of processing
+ * the add.
+ */
+ next = ISC_LIST_HEAD(temp_diff.tuples);
+ while (next != NULL) {
+ unsigned char *next_data = next->rdata.data;
+ unsigned char *tuple_data = tuple->rdata.data;
+ if (next->op != DNS_DIFFOP_DEL ||
+ next->rdata.length != tuple->rdata.length ||
+ next_data[0] != tuple_data[0] ||
+ next_data[2] != tuple_data[2] ||
+ next_data[3] != tuple_data[3] ||
+ memcmp(next_data + 4, tuple_data + 4,
+ tuple->rdata.length - 4))
+ {
+ next = ISC_LIST_NEXT(next, link);
+ continue;
+ }
+ ISC_LIST_UNLINK(temp_diff.tuples, next, link);
+ ISC_LIST_APPEND(diff->tuples, next, link);
+ next = ISC_LIST_HEAD(temp_diff.tuples);
+ }
+
+ /*
+ * Create a private-type record to signal that
+ * we want a delayed NSEC3 chain add/delete
+ */
+ dns_nsec3param_toprivate(&tuple->rdata, &rdata,
+ privatetype, buf, sizeof(buf));
+ buf[2] |= DNS_NSEC3FLAG_CREATE;
+
+ /*
+ * If the zone is not currently capable of
+ * supporting an NSEC3 chain, then we set the
+ * INITIAL flag to indicate that these parameters
+ * are to be used later.
+ *
+ * Don't provide a 'diff' here because we want to
+ * know the capability of the current database.
+ */
+ result = dns_nsec_nseconly(db, ver, NULL, &nseconly);
+ if (result == ISC_R_NOTFOUND || nseconly) {
+ buf[2] |= DNS_NSEC3FLAG_INITIAL;
+ }
+
+ /*
+ * See if this CREATE request already exists.
+ */
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+
+ if (!flag) {
+ CHECK(dns_difftuple_create(
+ diff->mctx, DNS_DIFFOP_ADD, name, 0,
+ &rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ }
+
+ /*
+ * Remove any existing CREATE request to add an
+ * otherwise identical chain with a reversed
+ * OPTOUT state.
+ */
+ buf[2] ^= DNS_NSEC3FLAG_OPTOUT;
+ 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));
+ }
+
+ /*
+ * Find the next tuple to be processed and remove the
+ * temporary add record.
+ */
+ next = ISC_LIST_NEXT(tuple, link);
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL,
+ name, ttl, &tuple->rdata,
+ &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ dns_diff_appendminimal(diff, &tuple);
+ dns_rdata_reset(&rdata);
+ } else {
+ next = ISC_LIST_NEXT(tuple, link);
+ }
+ }
+
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = next)
+ {
+ INSIST(ttl_good);
+
+ next = ISC_LIST_NEXT(tuple, link);
+ /*
+ * See if we already have a REMOVE request in progress.
+ */
+ dns_nsec3param_toprivate(&tuple->rdata, &rdata, privatetype,
+ buf, sizeof(buf));
+
+ buf[2] |= DNS_NSEC3FLAG_REMOVE | DNS_NSEC3FLAG_NONSEC;
+
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+ if (!flag) {
+ buf[2] &= ~DNS_NSEC3FLAG_NONSEC;
+ CHECK(rr_exists(db, ver, name, &rdata, &flag));
+ }
+
+ if (!flag) {
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
+ name, 0, &rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ }
+ CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD, name,
+ ttl, &tuple->rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, diff));
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ dns_diff_appendminimal(diff, &tuple);
+ dns_rdata_reset(&rdata);
+ }
+
+ result = ISC_R_SUCCESS;
+failure:
+ dns_diff_clear(&temp_diff);
+ return (result);
+}
+
+static isc_result_t
+rollback_private(dns_db_t *db, dns_rdatatype_t privatetype,
+ dns_dbversion_t *ver, dns_diff_t *diff) {
+ dns_diff_t temp_diff;
+ dns_diffop_t op;
+ dns_difftuple_t *tuple, *newtuple = NULL, *next;
+ dns_name_t *name = dns_db_origin(db);
+ isc_mem_t *mctx = diff->mctx;
+ isc_result_t result;
+
+ if (privatetype == 0) {
+ return (ISC_R_SUCCESS);
+ }
+
+ dns_diff_init(mctx, &temp_diff);
+
+ /*
+ * Extract the changes to be rolled back.
+ */
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) {
+ next = ISC_LIST_NEXT(tuple, link);
+
+ if (tuple->rdata.type != privatetype ||
+ !dns_name_equal(name, &tuple->name))
+ {
+ continue;
+ }
+
+ /*
+ * Allow records which indicate that a zone has been
+ * signed with a DNSKEY to be removed.
+ */
+ if (tuple->op == DNS_DIFFOP_DEL && tuple->rdata.length == 5 &&
+ tuple->rdata.data[0] != 0 && tuple->rdata.data[4] != 0)
+ {
+ continue;
+ }
+
+ ISC_LIST_UNLINK(diff->tuples, tuple, link);
+ ISC_LIST_PREPEND(temp_diff.tuples, tuple, link);
+ }
+
+ /*
+ * Rollback the changes.
+ */
+ while ((tuple = ISC_LIST_HEAD(temp_diff.tuples)) != NULL) {
+ op = (tuple->op == DNS_DIFFOP_DEL) ? DNS_DIFFOP_ADD
+ : DNS_DIFFOP_DEL;
+ CHECK(dns_difftuple_create(mctx, op, name, tuple->ttl,
+ &tuple->rdata, &newtuple));
+ CHECK(do_one_tuple(&newtuple, db, ver, &temp_diff));
+ }
+ result = ISC_R_SUCCESS;
+
+failure:
+ dns_diff_clear(&temp_diff);
+ return (result);
+}
+
+/*
+ * Add records to cause the delayed signing of the zone by added DNSKEY
+ * to remove the RRSIG records generated by a deleted DNSKEY.
+ */
+static isc_result_t
+add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype,
+ dns_dbversion_t *ver, dns_diff_t *diff) {
+ dns_difftuple_t *tuple, *newtuple = NULL, *next;
+ 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);
+ dns_diff_t temp_diff;
+
+ dns_diff_init(diff->mctx, &temp_diff);
+
+ /*
+ * Extract the DNSKEY tuples from the list.
+ */
+ for (tuple = ISC_LIST_HEAD(diff->tuples); tuple != NULL; tuple = next) {
+ next = ISC_LIST_NEXT(tuple, link);
+
+ if (tuple->rdata.type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ ISC_LIST_UNLINK(diff->tuples, tuple, link);
+ ISC_LIST_APPEND(temp_diff.tuples, tuple, link);
+ }
+
+ /*
+ * Extract TTL changes pairs, we don't need signing records for these.
+ */
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = next)
+ {
+ if (tuple->op == DNS_DIFFOP_ADD) {
+ /*
+ * Walk the temp_diff list looking for the
+ * corresponding delete.
+ */
+ next = ISC_LIST_HEAD(temp_diff.tuples);
+ while (next != NULL) {
+ unsigned char *next_data = next->rdata.data;
+ unsigned char *tuple_data = tuple->rdata.data;
+ if (next->op == DNS_DIFFOP_DEL &&
+ dns_name_equal(&tuple->name, &next->name) &&
+ next->rdata.length == tuple->rdata.length &&
+ !memcmp(next_data, tuple_data,
+ next->rdata.length))
+ {
+ ISC_LIST_UNLINK(temp_diff.tuples, next,
+ link);
+ ISC_LIST_APPEND(diff->tuples, next,
+ link);
+ break;
+ }
+ next = ISC_LIST_NEXT(next, link);
+ }
+ /*
+ * If we have not found a pair move onto the next
+ * tuple.
+ */
+ if (next == NULL) {
+ next = ISC_LIST_NEXT(tuple, link);
+ continue;
+ }
+ /*
+ * Find the next tuple to be processed before
+ * unlinking then complete moving the pair to 'diff'.
+ */
+ next = ISC_LIST_NEXT(tuple, link);
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ ISC_LIST_APPEND(diff->tuples, tuple, link);
+ } else {
+ next = ISC_LIST_NEXT(tuple, link);
+ }
+ }
+
+ /*
+ * Process the remaining DNSKEY entries.
+ */
+ for (tuple = ISC_LIST_HEAD(temp_diff.tuples); tuple != NULL;
+ tuple = ISC_LIST_HEAD(temp_diff.tuples))
+ {
+ ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
+ ISC_LIST_APPEND(diff->tuples, tuple, link);
+
+ 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;
+
+ 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:
+ dns_diff_clear(&temp_diff);
+ return (result);
+}
+
+static bool
+isdnssec(dns_db_t *db, dns_dbversion_t *ver, dns_rdatatype_t privatetype) {
+ isc_result_t result;
+ bool build_nsec, build_nsec3;
+
+ if (dns_db_issecure(db)) {
+ return (true);
+ }
+
+ result = dns_private_chains(db, ver, privatetype, &build_nsec,
+ &build_nsec3);
+ RUNTIME_CHECK(result == ISC_R_SUCCESS);
+ return (build_nsec || build_nsec3);
+}
+
+static void
+update_action(isc_task_t *task, isc_event_t *event) {
+ update_event_t *uev = (update_event_t *)event;
+ dns_zone_t *zone = uev->zone;
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
+ const dns_ssurule_t **rules = uev->rules;
+ size_t rule = 0, ruleslen = uev->ruleslen;
+ isc_result_t result;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *oldver = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_diff_t diff; /* Pending updates. */
+ dns_diff_t temp; /* Pending RR existence assertions. */
+ bool soa_serial_changed = false;
+ isc_mem_t *mctx = client->mctx;
+ dns_rdatatype_t covers;
+ dns_message_t *request = client->message;
+ dns_rdataclass_t zoneclass;
+ dns_name_t *zonename = NULL;
+ dns_ssutable_t *ssutable = NULL;
+ dns_fixedname_t tmpnamefixed;
+ dns_name_t *tmpname = NULL;
+ dns_zoneopt_t options;
+ dns_difftuple_t *tuple;
+ dns_rdata_dnskey_t dnskey;
+ bool had_dnskey;
+ dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
+ dns_ttl_t maxttl = 0;
+ uint32_t maxrecords;
+ uint64_t records;
+
+ INSIST(event->ev_type == DNS_EVENT_UPDATE);
+
+ dns_diff_init(mctx, &diff);
+ dns_diff_init(mctx, &temp);
+
+ CHECK(dns_zone_getdb(zone, &db));
+ zonename = dns_db_origin(db);
+ zoneclass = dns_db_class(db);
+ dns_zone_getssutable(zone, &ssutable);
+ options = dns_zone_getoptions(zone);
+
+ /*
+ * Get old and new versions now that queryacl has been checked.
+ */
+ dns_db_currentversion(db, &oldver);
+ CHECK(dns_db_newversion(db, &ver));
+
+ /*
+ * Check prerequisites.
+ */
+
+ for (result = dns_message_firstname(request, DNS_SECTION_PREREQUISITE);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(request, DNS_SECTION_PREREQUISITE))
+ {
+ dns_name_t *name = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_ttl_t ttl;
+ dns_rdataclass_t update_class;
+ bool flag;
+
+ get_current_rr(request, DNS_SECTION_PREREQUISITE, zoneclass,
+ &name, &rdata, &covers, &ttl, &update_class);
+
+ if (ttl != 0) {
+ PREREQFAILC(DNS_R_FORMERR, "prerequisite TTL is not "
+ "zero");
+ }
+
+ if (!dns_name_issubdomain(name, zonename)) {
+ PREREQFAILN(DNS_R_NOTZONE, name,
+ "prerequisite name is out of zone");
+ }
+
+ if (update_class == dns_rdataclass_any) {
+ if (rdata.length != 0) {
+ PREREQFAILC(DNS_R_FORMERR, "class ANY "
+ "prerequisite "
+ "RDATA is not "
+ "empty");
+ }
+ if (rdata.type == dns_rdatatype_any) {
+ CHECK(name_exists(db, ver, name, &flag));
+ if (!flag) {
+ PREREQFAILN(DNS_R_NXDOMAIN, name,
+ "'name in use' "
+ "prerequisite not "
+ "satisfied");
+ }
+ } else {
+ CHECK(rrset_exists(db, ver, name, rdata.type,
+ covers, &flag));
+ if (!flag) {
+ /* RRset does not exist. */
+ PREREQFAILNT(DNS_R_NXRRSET, name,
+ rdata.type,
+ "'rrset exists (value "
+ "independent)' "
+ "prerequisite not "
+ "satisfied");
+ }
+ }
+ } else if (update_class == dns_rdataclass_none) {
+ if (rdata.length != 0) {
+ PREREQFAILC(DNS_R_FORMERR, "class NONE "
+ "prerequisite "
+ "RDATA is not "
+ "empty");
+ }
+ if (rdata.type == dns_rdatatype_any) {
+ CHECK(name_exists(db, ver, name, &flag));
+ if (flag) {
+ PREREQFAILN(DNS_R_YXDOMAIN, name,
+ "'name not in use' "
+ "prerequisite not "
+ "satisfied");
+ }
+ } else {
+ CHECK(rrset_exists(db, ver, name, rdata.type,
+ covers, &flag));
+ if (flag) {
+ /* RRset exists. */
+ PREREQFAILNT(DNS_R_YXRRSET, name,
+ rdata.type,
+ "'rrset does not exist' "
+ "prerequisite not "
+ "satisfied");
+ }
+ }
+ } else if (update_class == zoneclass) {
+ /* "temp<rr.name, rr.type> += rr;" */
+ result = temp_append(&temp, name, &rdata);
+ if (result != ISC_R_SUCCESS) {
+ UNEXPECTED_ERROR(
+ "temp entry creation failed: %s",
+ isc_result_totext(result));
+ FAIL(ISC_R_UNEXPECTED);
+ }
+ } else {
+ PREREQFAILC(DNS_R_FORMERR, "malformed prerequisite");
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ FAIL(result);
+ }
+
+ /*
+ * Perform the final check of the "rrset exists (value dependent)"
+ * prerequisites.
+ */
+ if (ISC_LIST_HEAD(temp.tuples) != NULL) {
+ dns_rdatatype_t type;
+
+ /*
+ * Sort the prerequisite records by owner name,
+ * type, and rdata.
+ */
+ result = dns_diff_sort(&temp, temp_order);
+ if (result != ISC_R_SUCCESS) {
+ FAILC(result, "'RRset exists (value dependent)' "
+ "prerequisite not satisfied");
+ }
+
+ tmpname = dns_fixedname_initname(&tmpnamefixed);
+ result = temp_check(mctx, &temp, db, ver, tmpname, &type);
+ if (result != ISC_R_SUCCESS) {
+ FAILNT(result, tmpname, type,
+ "'RRset exists (value dependent)' "
+ "prerequisite not satisfied");
+ }
+ }
+
+ update_log(client, zone, LOGLEVEL_DEBUG, "prerequisites are OK");
+
+ /*
+ * Process the Update Section.
+ */
+ INSIST(ssutable == NULL || rules != NULL);
+ for (rule = 0,
+ result = dns_message_firstname(request, DNS_SECTION_UPDATE);
+ result == ISC_R_SUCCESS;
+ rule++, result = dns_message_nextname(request, DNS_SECTION_UPDATE))
+ {
+ dns_name_t *name = NULL;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dns_ttl_t ttl;
+ dns_rdataclass_t update_class;
+ bool flag;
+
+ INSIST(ssutable == NULL || rule < ruleslen);
+
+ get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
+ &rdata, &covers, &ttl, &update_class);
+
+ if (update_class == zoneclass) {
+ unsigned int max = 0;
+
+ /*
+ * RFC1123 doesn't allow MF and MD in master files.
+ */
+ if (rdata.type == dns_rdatatype_md ||
+ rdata.type == dns_rdatatype_mf)
+ {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_rdatatype_format(rdata.type, typebuf,
+ sizeof(typebuf));
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "attempt to add %s ignored",
+ typebuf);
+ continue;
+ }
+ if ((rdata.type == dns_rdatatype_ns ||
+ rdata.type == dns_rdatatype_dname) &&
+ dns_name_iswildcard(name))
+ {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_rdatatype_format(rdata.type, typebuf,
+ sizeof(typebuf));
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "attempt to add wildcard %s record "
+ "ignored",
+ typebuf);
+ continue;
+ }
+ if (rdata.type == dns_rdatatype_cname) {
+ CHECK(cname_incompatible_rrset_exists(
+ db, ver, name, &flag));
+ if (flag) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to add CNAME "
+ "alongside non-CNAME "
+ "ignored");
+ continue;
+ }
+ } else {
+ CHECK(rrset_exists(db, ver, name,
+ dns_rdatatype_cname, 0,
+ &flag));
+ if (flag && !dns_rdatatype_atcname(rdata.type))
+ {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to add non-CNAME "
+ "alongside CNAME ignored");
+ continue;
+ }
+ }
+ if (rdata.type == dns_rdatatype_soa) {
+ bool ok;
+ CHECK(rrset_exists(db, ver, name,
+ dns_rdatatype_soa, 0,
+ &flag));
+ if (!flag) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to create 2nd "
+ "SOA ignored");
+ continue;
+ }
+ CHECK(check_soa_increment(db, ver, &rdata,
+ &ok));
+ if (!ok) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "SOA update failed to "
+ "increment serial, "
+ "ignoring it");
+ continue;
+ }
+ soa_serial_changed = true;
+ }
+
+ if (dns_rdatatype_atparent(rdata.type) &&
+ dns_name_equal(name, zonename))
+ {
+ char typebuf[DNS_RDATATYPE_FORMATSIZE];
+
+ dns_rdatatype_format(rdata.type, typebuf,
+ sizeof(typebuf));
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "attempt to add a %s record at "
+ "zone apex ignored",
+ typebuf);
+ continue;
+ }
+
+ if (rdata.type == privatetype) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "attempt to add a private type "
+ "(%u) record rejected internal "
+ "use only",
+ privatetype);
+ continue;
+ }
+
+ if (rdata.type == dns_rdatatype_nsec3param) {
+ /*
+ * Ignore attempts to add NSEC3PARAM records
+ * with any flags other than OPTOUT.
+ */
+ if ((rdata.data[1] & ~DNS_NSEC3FLAG_OPTOUT) !=
+ 0)
+ {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to add NSEC3PARAM "
+ "record with non OPTOUT "
+ "flag");
+ continue;
+ }
+ }
+
+ if ((options & DNS_ZONEOPT_CHECKWILDCARD) != 0 &&
+ dns_name_internalwildcard(name))
+ {
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namestr, sizeof(namestr));
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "warning: ownername '%s' contains "
+ "a non-terminal wildcard",
+ namestr);
+ }
+
+ if ((options & DNS_ZONEOPT_CHECKTTL) != 0) {
+ maxttl = dns_zone_getmaxttl(zone);
+ if (ttl > maxttl) {
+ ttl = maxttl;
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "reducing TTL to the "
+ "configured max-zone-ttl %d",
+ maxttl);
+ }
+ }
+
+ if (rules != NULL && rules[rule] != NULL) {
+ max = dns_ssurule_max(rules[rule], rdata.type);
+ }
+ if (max != 0) {
+ unsigned int count = 0;
+ CHECK(foreach_rr(db, ver, name, rdata.type,
+ covers, count_action, &count));
+ if (count >= max) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to add more "
+ "records than permitted by "
+ "policy max=%u",
+ max);
+ continue;
+ }
+ }
+
+ if (isc_log_wouldlog(ns_lctx, LOGLEVEL_PROTOCOL)) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char typestr[DNS_RDATATYPE_FORMATSIZE];
+ char rdstr[2048];
+ isc_buffer_t buf;
+ int len = 0;
+ const char *truncated = "";
+
+ dns_name_format(name, namestr, sizeof(namestr));
+ dns_rdatatype_format(rdata.type, typestr,
+ sizeof(typestr));
+ isc_buffer_init(&buf, rdstr, sizeof(rdstr));
+ result = dns_rdata_totext(&rdata, NULL, &buf);
+ if (result == ISC_R_NOSPACE) {
+ len = (int)isc_buffer_usedlength(&buf);
+ truncated = " [TRUNCATED]";
+ } else if (result != ISC_R_SUCCESS) {
+ snprintf(rdstr, sizeof(rdstr),
+ "[dns_"
+ "rdata_totext failed: %s]",
+ isc_result_totext(result));
+ len = strlen(rdstr);
+ } else {
+ len = (int)isc_buffer_usedlength(&buf);
+ }
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "adding an RR at '%s' %s %.*s%s",
+ namestr, typestr, len, rdstr,
+ truncated);
+ }
+
+ /* Prepare the affected RRset for the addition. */
+ {
+ add_rr_prepare_ctx_t ctx;
+ ctx.db = db;
+ ctx.ver = ver;
+ ctx.diff = &diff;
+ ctx.name = name;
+ ctx.oldname = name;
+ ctx.update_rr = &rdata;
+ ctx.update_rr_ttl = ttl;
+ ctx.ignore_add = false;
+ dns_diff_init(mctx, &ctx.del_diff);
+ dns_diff_init(mctx, &ctx.add_diff);
+ CHECK(foreach_rr(db, ver, name, rdata.type,
+ covers, add_rr_prepare_action,
+ &ctx));
+
+ if (ctx.ignore_add) {
+ dns_diff_clear(&ctx.del_diff);
+ dns_diff_clear(&ctx.add_diff);
+ } else {
+ result = do_diff(&ctx.del_diff, db, ver,
+ &diff);
+ if (result == ISC_R_SUCCESS) {
+ result = do_diff(&ctx.add_diff,
+ db, ver,
+ &diff);
+ }
+ if (result != ISC_R_SUCCESS) {
+ dns_diff_clear(&ctx.del_diff);
+ dns_diff_clear(&ctx.add_diff);
+ goto failure;
+ }
+ CHECK(update_one_rr(db, ver, &diff,
+ DNS_DIFFOP_ADD,
+ name, ttl, &rdata));
+ }
+ }
+ } else if (update_class == dns_rdataclass_any) {
+ if (rdata.type == dns_rdatatype_any) {
+ if (isc_log_wouldlog(ns_lctx,
+ LOGLEVEL_PROTOCOL))
+ {
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_name_format(name, namestr,
+ sizeof(namestr));
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "delete all rrsets from "
+ "name '%s'",
+ namestr);
+ }
+ if (dns_name_equal(name, zonename)) {
+ CHECK(delete_if(type_not_soa_nor_ns_p,
+ db, ver, name,
+ dns_rdatatype_any, 0,
+ &rdata, &diff));
+ } else {
+ CHECK(delete_if(type_not_dnssec, db,
+ ver, name,
+ dns_rdatatype_any, 0,
+ &rdata, &diff));
+ }
+ } else if (dns_name_equal(name, zonename) &&
+ (rdata.type == dns_rdatatype_soa ||
+ rdata.type == dns_rdatatype_ns))
+ {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "attempt to delete all SOA "
+ "or NS records ignored");
+ continue;
+ } else {
+ if (isc_log_wouldlog(ns_lctx,
+ LOGLEVEL_PROTOCOL))
+ {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char typestr[DNS_RDATATYPE_FORMATSIZE];
+ dns_name_format(name, namestr,
+ sizeof(namestr));
+ dns_rdatatype_format(rdata.type,
+ typestr,
+ sizeof(typestr));
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "deleting rrset at '%s' %s",
+ namestr, typestr);
+ }
+ CHECK(delete_if(true_p, db, ver, name,
+ rdata.type, covers, &rdata,
+ &diff));
+ }
+ } else if (update_class == dns_rdataclass_none) {
+ char namestr[DNS_NAME_FORMATSIZE];
+ char typestr[DNS_RDATATYPE_FORMATSIZE];
+
+ /*
+ * The (name == zonename) condition appears in
+ * RFC2136 3.4.2.4 but is missing from the pseudocode.
+ */
+ if (dns_name_equal(name, zonename)) {
+ if (rdata.type == dns_rdatatype_soa) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to delete SOA "
+ "ignored");
+ continue;
+ }
+ if (rdata.type == dns_rdatatype_ns) {
+ int count;
+ CHECK(rr_count(db, ver, name,
+ dns_rdatatype_ns, 0,
+ &count));
+ if (count == 1) {
+ update_log(client, zone,
+ LOGLEVEL_PROTOCOL,
+ "attempt to "
+ "delete last "
+ "NS ignored");
+ continue;
+ }
+ }
+ }
+ dns_name_format(name, namestr, sizeof(namestr));
+ dns_rdatatype_format(rdata.type, typestr,
+ sizeof(typestr));
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "deleting an RR at %s %s", namestr, typestr);
+ CHECK(delete_if(rr_equal_p, db, ver, name, rdata.type,
+ covers, &rdata, &diff));
+ }
+ }
+ if (result != ISC_R_NOMORE) {
+ FAIL(result);
+ }
+
+ /*
+ * Check that any changes to DNSKEY/NSEC3PARAM records make sense.
+ * If they don't then back out all changes to DNSKEY/NSEC3PARAM
+ * records.
+ */
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ CHECK(check_dnssec(client, zone, db, ver, &diff));
+ }
+
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ unsigned int errors = 0;
+ CHECK(dns_zone_nscheck(zone, db, ver, &errors));
+ if (errors != 0) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "update rejected: post update name server "
+ "sanity check failed");
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+ }
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ result = dns_zone_cdscheck(zone, db, ver);
+ if (result == DNS_R_BADCDS || result == DNS_R_BADCDNSKEY) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "update rejected: bad %s RRset",
+ result == DNS_R_BADCDS ? "CDS" : "CDNSKEY");
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ }
+
+ /*
+ * If any changes were made, increment the SOA serial number,
+ * update RRSIGs and NSECs (if zone is secure), and write the update
+ * to the journal.
+ */
+ if (!ISC_LIST_EMPTY(diff.tuples)) {
+ char *journalfile;
+ dns_journal_t *journal;
+ bool has_dnskey;
+
+ /*
+ * Increment the SOA serial, but only if it was not
+ * changed as a result of an update operation.
+ */
+ if (!soa_serial_changed) {
+ CHECK(update_soa_serial(
+ db, ver, &diff, mctx,
+ dns_zone_getserialupdatemethod(zone)));
+ }
+
+ CHECK(check_mx(client, zone, db, ver, &diff));
+
+ CHECK(remove_orphaned_ds(db, ver, &diff));
+
+ CHECK(rrset_exists(db, ver, zonename, dns_rdatatype_dnskey, 0,
+ &has_dnskey));
+
+#define ALLOW_SECURE_TO_INSECURE(zone) \
+ ((dns_zone_getoptions(zone) & DNS_ZONEOPT_SECURETOINSECURE) != 0)
+
+ CHECK(rrset_exists(db, oldver, zonename, dns_rdatatype_dnskey,
+ 0, &had_dnskey));
+ if (!ALLOW_SECURE_TO_INSECURE(zone)) {
+ if (had_dnskey && !has_dnskey) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "update rejected: all DNSKEY "
+ "records removed and "
+ "'dnssec-secure-to-insecure' "
+ "not set");
+ result = DNS_R_REFUSED;
+ goto failure;
+ }
+ }
+
+ CHECK(rollback_private(db, privatetype, ver, &diff));
+
+ CHECK(add_signing_records(db, privatetype, ver, &diff));
+
+ CHECK(add_nsec3param_records(client, zone, db, ver, &diff));
+
+ if (had_dnskey && !has_dnskey) {
+ /*
+ * We are transitioning from secure to insecure.
+ * Cause all NSEC3 chains to be deleted. When the
+ * the last signature for the DNSKEY records are
+ * remove any NSEC chain present will also be removed.
+ */
+ CHECK(dns_nsec3param_deletechains(db, ver, zone, true,
+ &diff));
+ } else if (has_dnskey && isdnssec(db, ver, privatetype)) {
+ dns_update_log_t log;
+ uint32_t interval =
+ dns_zone_getsigvalidityinterval(zone);
+
+ log.func = update_log_cb;
+ log.arg = client;
+ result = dns_update_signatures(&log, zone, db, oldver,
+ ver, &diff, interval);
+
+ if (result != ISC_R_SUCCESS) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "RRSIG/NSEC/NSEC3 update failed: %s",
+ isc_result_totext(result));
+ goto failure;
+ }
+ }
+
+ maxrecords = dns_zone_getmaxrecords(zone);
+ if (maxrecords != 0U) {
+ result = dns_db_getsize(db, ver, &records, NULL);
+ if (result == ISC_R_SUCCESS && records > maxrecords) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "records in zone (%" PRIu64 ") "
+ "exceeds max-records (%u)",
+ records, maxrecords);
+ result = DNS_R_TOOMANYRECORDS;
+ goto failure;
+ }
+ }
+
+ journalfile = dns_zone_getjournal(zone);
+ if (journalfile != NULL) {
+ update_log(client, zone, LOGLEVEL_DEBUG,
+ "writing journal %s", journalfile);
+
+ journal = NULL;
+ result = dns_journal_open(mctx, journalfile,
+ DNS_JOURNAL_CREATE, &journal);
+ if (result != ISC_R_SUCCESS) {
+ FAILS(result, "journal open failed");
+ }
+
+ result = dns_journal_write_transaction(journal, &diff);
+ if (result != ISC_R_SUCCESS) {
+ dns_journal_destroy(&journal);
+ FAILS(result, "journal write failed");
+ }
+
+ dns_journal_destroy(&journal);
+ }
+
+ /*
+ * XXXRTH Just a note that this committing code will have
+ * to change to handle databases that need two-phase
+ * commit, but this isn't a priority.
+ */
+ update_log(client, zone, LOGLEVEL_DEBUG,
+ "committing update transaction");
+
+ dns_db_closeversion(db, &ver, true);
+
+ /*
+ * Mark the zone as dirty so that it will be written to disk.
+ */
+ dns_zone_markdirty(zone);
+
+ /*
+ * Notify secondaries of the change we just made.
+ */
+ dns_zone_notify(zone);
+
+ /*
+ * Cause the zone to be signed with the key that we
+ * have just added or have the corresponding signatures
+ * deleted.
+ *
+ * Note: we are already committed to this course of action.
+ */
+ for (tuple = ISC_LIST_HEAD(diff.tuples); tuple != NULL;
+ tuple = ISC_LIST_NEXT(tuple, link))
+ {
+ isc_region_t r;
+ dns_secalg_t algorithm;
+ uint16_t keyid;
+
+ if (tuple->rdata.type != dns_rdatatype_dnskey) {
+ continue;
+ }
+
+ dns_rdata_tostruct(&tuple->rdata, &dnskey, NULL);
+ if ((dnskey.flags &
+ (DNS_KEYFLAG_OWNERMASK | DNS_KEYTYPE_NOAUTH)) !=
+ DNS_KEYOWNER_ZONE)
+ {
+ continue;
+ }
+
+ dns_rdata_toregion(&tuple->rdata, &r);
+ algorithm = dnskey.algorithm;
+ keyid = dst_region_computeid(&r);
+
+ result = dns_zone_signwithkey(
+ zone, algorithm, keyid,
+ (tuple->op == DNS_DIFFOP_DEL));
+ if (result != ISC_R_SUCCESS) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "dns_zone_signwithkey failed: %s",
+ isc_result_totext(result));
+ }
+ }
+
+ /*
+ * Cause the zone to add/delete NSEC3 chains for the
+ * deferred NSEC3PARAM changes.
+ *
+ * Note: we are already committed to this course of action.
+ */
+ for (tuple = ISC_LIST_HEAD(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 != privatetype ||
+ tuple->op != DNS_DIFFOP_ADD)
+ {
+ continue;
+ }
+
+ if (!dns_nsec3param_fromprivate(&tuple->rdata, &rdata,
+ buf, sizeof(buf)))
+ {
+ continue;
+ }
+ dns_rdata_tostruct(&rdata, &nsec3param, NULL);
+ if (nsec3param.flags == 0) {
+ continue;
+ }
+
+ result = dns_zone_addnsec3chain(zone, &nsec3param);
+ if (result != ISC_R_SUCCESS) {
+ update_log(client, zone, ISC_LOG_ERROR,
+ "dns_zone_addnsec3chain failed: %s",
+ isc_result_totext(result));
+ }
+ }
+ } else {
+ update_log(client, zone, LOGLEVEL_DEBUG, "redundant request");
+ dns_db_closeversion(db, &ver, true);
+ }
+ result = ISC_R_SUCCESS;
+ goto common;
+
+failure:
+ /*
+ * The reason for failure should have been logged at this point.
+ */
+ if (ver != NULL) {
+ update_log(client, zone, LOGLEVEL_DEBUG, "rolling back");
+ dns_db_closeversion(db, &ver, false);
+ }
+
+common:
+ dns_diff_clear(&temp);
+ dns_diff_clear(&diff);
+
+ if (oldver != NULL) {
+ dns_db_closeversion(db, &oldver, false);
+ }
+
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+
+ if (rules != NULL) {
+ isc_mem_put(mctx, rules, sizeof(*rules) * ruleslen);
+ }
+
+ if (ssutable != NULL) {
+ dns_ssutable_detach(&ssutable);
+ }
+
+ isc_task_detach(&task);
+ uev->result = result;
+ if (zone != NULL) {
+ INSIST(uev->zone == zone); /* we use this later */
+ }
+ uev->ev_type = DNS_EVENT_UPDATEDONE;
+ uev->ev_action = updatedone_action;
+
+ isc_task_send(client->task, &event);
+
+ INSIST(ver == NULL);
+ INSIST(event == NULL);
+}
+
+static void
+updatedone_action(isc_task_t *task, isc_event_t *event) {
+ update_event_t *uev = (update_event_t *)event;
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ REQUIRE(event->ev_type == DNS_EVENT_UPDATEDONE);
+ REQUIRE(task == client->task);
+ REQUIRE(client->updatehandle == client->handle);
+
+ INSIST(client->nupdates > 0);
+ switch (uev->result) {
+ case ISC_R_SUCCESS:
+ inc_stats(client, uev->zone, ns_statscounter_updatedone);
+ break;
+ case DNS_R_REFUSED:
+ inc_stats(client, uev->zone, ns_statscounter_updaterej);
+ break;
+ default:
+ inc_stats(client, uev->zone, ns_statscounter_updatefail);
+ break;
+ }
+ if (uev->zone != NULL) {
+ dns_zone_detach(&uev->zone);
+ }
+
+ client->nupdates--;
+
+ respond(client, uev->result);
+
+ isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota });
+ isc_event_free(&event);
+ isc_nmhandle_detach(&client->updatehandle);
+}
+
+/*%
+ * Update forwarding support.
+ */
+static void
+forward_fail(isc_task_t *task, isc_event_t *event) {
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ INSIST(client->nupdates > 0);
+ client->nupdates--;
+ respond(client, DNS_R_SERVFAIL);
+
+ isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota });
+ isc_event_free(&event);
+ isc_nmhandle_detach(&client->updatehandle);
+}
+
+static void
+forward_callback(void *arg, isc_result_t result, dns_message_t *answer) {
+ update_event_t *uev = arg;
+ ns_client_t *client = uev->ev_arg;
+ dns_zone_t *zone = uev->zone;
+
+ if (result != ISC_R_SUCCESS) {
+ INSIST(answer == NULL);
+ uev->ev_type = DNS_EVENT_UPDATEDONE;
+ uev->ev_action = forward_fail;
+ inc_stats(client, zone, ns_statscounter_updatefwdfail);
+ } else {
+ uev->ev_type = DNS_EVENT_UPDATEDONE;
+ uev->ev_action = forward_done;
+ uev->answer = answer;
+ inc_stats(client, zone, ns_statscounter_updaterespfwd);
+ }
+
+ isc_task_send(client->task, ISC_EVENT_PTR(&uev));
+ dns_zone_detach(&zone);
+}
+
+static void
+forward_done(isc_task_t *task, isc_event_t *event) {
+ update_event_t *uev = (update_event_t *)event;
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
+
+ UNUSED(task);
+
+ INSIST(client->nupdates > 0);
+ client->nupdates--;
+ ns_client_sendraw(client, uev->answer);
+ dns_message_detach(&uev->answer);
+
+ isc_quota_detach(&(isc_quota_t *){ &client->manager->sctx->updquota });
+ isc_event_free(&event);
+ isc_nmhandle_detach(&client->reqhandle);
+ isc_nmhandle_detach(&client->updatehandle);
+}
+
+static void
+forward_action(isc_task_t *task, isc_event_t *event) {
+ update_event_t *uev = (update_event_t *)event;
+ dns_zone_t *zone = uev->zone;
+ ns_client_t *client = (ns_client_t *)event->ev_arg;
+ isc_result_t result;
+
+ result = dns_zone_forwardupdate(zone, client->message, forward_callback,
+ event);
+ if (result != ISC_R_SUCCESS) {
+ uev->ev_type = DNS_EVENT_UPDATEDONE;
+ uev->ev_action = forward_fail;
+ isc_task_send(client->task, &event);
+ inc_stats(client, zone, ns_statscounter_updatefwdfail);
+ dns_zone_detach(&zone);
+ } else {
+ inc_stats(client, zone, ns_statscounter_updatereqfwd);
+ }
+
+ isc_task_detach(&task);
+}
+
+static isc_result_t
+send_forward_event(ns_client_t *client, dns_zone_t *zone) {
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+ isc_result_t result = ISC_R_SUCCESS;
+ update_event_t *event = NULL;
+ isc_task_t *zonetask = NULL;
+
+ result = checkupdateacl(client, dns_zone_getforwardacl(zone),
+ "update forwarding", dns_zone_getorigin(zone),
+ true, false);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+
+ result = isc_quota_attach(&client->manager->sctx->updquota,
+ &(isc_quota_t *){ NULL });
+ if (result != ISC_R_SUCCESS) {
+ update_log(client, zone, LOGLEVEL_PROTOCOL,
+ "update failed: too many DNS UPDATEs queued (%s)",
+ isc_result_totext(result));
+ ns_stats_increment(client->manager->sctx->nsstats,
+ ns_statscounter_updatequota);
+ return (DNS_R_DROP);
+ }
+
+ event = (update_event_t *)isc_event_allocate(
+ client->mctx, client, DNS_EVENT_UPDATE, forward_action, NULL,
+ sizeof(*event));
+ event->zone = zone;
+ event->result = ISC_R_SUCCESS;
+
+ INSIST(client->nupdates == 0);
+ client->nupdates++;
+ event->ev_arg = client;
+
+ dns_name_format(dns_zone_getorigin(zone), namebuf, sizeof(namebuf));
+ dns_rdataclass_format(dns_zone_getclass(zone), classbuf,
+ sizeof(classbuf));
+
+ ns_client_log(client, NS_LOGCATEGORY_UPDATE, NS_LOGMODULE_UPDATE,
+ LOGLEVEL_PROTOCOL, "forwarding update for zone '%s/%s'",
+ namebuf, classbuf);
+
+ dns_zone_gettask(zone, &zonetask);
+ isc_nmhandle_attach(client->handle, &client->updatehandle);
+ isc_task_send(zonetask, ISC_EVENT_PTR(&event));
+
+ if (event != NULL) {
+ isc_event_free(ISC_EVENT_PTR(&event));
+ }
+ return (result);
+}
diff --git a/lib/ns/xfrout.c b/lib/ns/xfrout.c
new file mode 100644
index 0000000..9380924
--- /dev/null
+++ b/lib/ns/xfrout.c
@@ -0,0 +1,1829 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you 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/formatcheck.h>
+#include <isc/mem.h>
+#include <isc/netmgr.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/stats.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dlz.h>
+#include <dns/fixedname.h>
+#include <dns/journal.h>
+#include <dns/message.h>
+#include <dns/peer.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rriterator.h>
+#include <dns/soa.h>
+#include <dns/stats.h>
+#include <dns/tsig.h>
+#include <dns/view.h>
+#include <dns/zone.h>
+#include <dns/zt.h>
+
+#include <ns/client.h>
+#include <ns/log.h>
+#include <ns/server.h>
+#include <ns/stats.h>
+#include <ns/xfrout.h>
+
+/*! \file
+ * \brief
+ * Outgoing AXFR and IXFR.
+ */
+
+/*
+ * TODO:
+ * - IXFR over UDP
+ */
+
+#define XFROUT_COMMON_LOGARGS \
+ ns_lctx, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT
+
+#define XFROUT_PROTOCOL_LOGARGS XFROUT_COMMON_LOGARGS, ISC_LOG_INFO
+
+#define XFROUT_DEBUG_LOGARGS(n) XFROUT_COMMON_LOGARGS, ISC_LOG_DEBUG(n)
+
+#define XFROUT_RR_LOGARGS XFROUT_COMMON_LOGARGS, XFROUT_RR_LOGLEVEL
+
+#define XFROUT_RR_LOGLEVEL ISC_LOG_DEBUG(8)
+
+/*%
+ * 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 { \
+ result = (code); \
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
+ NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
+ "bad zone transfer request: %s (%s)", msg, \
+ isc_result_totext(code)); \
+ if (result != ISC_R_SUCCESS) \
+ goto failure; \
+ } while (0)
+
+#define FAILQ(code, msg, question, rdclass) \
+ do { \
+ char _buf1[DNS_NAME_FORMATSIZE]; \
+ char _buf2[DNS_RDATACLASS_FORMATSIZE]; \
+ result = (code); \
+ dns_name_format(question, _buf1, sizeof(_buf1)); \
+ dns_rdataclass_format(rdclass, _buf2, sizeof(_buf2)); \
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
+ NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
+ "bad zone transfer request: '%s/%s': %s (%s)", \
+ _buf1, _buf2, msg, isc_result_totext(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)
+
+/**************************************************************************/
+
+static void
+inc_stats(ns_client_t *client, dns_zone_t *zone, isc_statscounter_t counter) {
+ ns_stats_increment(client->sctx->nsstats, counter);
+ if (zone != NULL) {
+ isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
+ if (zonestats != NULL) {
+ isc_stats_increment(zonestats, counter);
+ }
+ }
+}
+
+/**************************************************************************/
+
+/*% Log an RR (for debugging) */
+
+static void
+log_rr(dns_name_t *name, dns_rdata_t *rdata, uint32_t ttl) {
+ isc_result_t result;
+ isc_buffer_t buf;
+ char mem[2000];
+ dns_rdatalist_t rdl;
+ dns_rdataset_t rds;
+ dns_rdata_t rd = DNS_RDATA_INIT;
+
+ dns_rdatalist_init(&rdl);
+ rdl.type = rdata->type;
+ rdl.rdclass = rdata->rdclass;
+ rdl.ttl = ttl;
+ if (rdata->type == dns_rdatatype_sig ||
+ rdata->type == dns_rdatatype_rrsig)
+ {
+ rdl.covers = dns_rdata_covers(rdata);
+ } else {
+ rdl.covers = dns_rdatatype_none;
+ }
+ dns_rdataset_init(&rds);
+ dns_rdata_init(&rd);
+ dns_rdata_clone(rdata, &rd);
+ ISC_LIST_APPEND(rdl.rdata, &rd, link);
+ RUNTIME_CHECK(dns_rdatalist_tordataset(&rdl, &rds) == ISC_R_SUCCESS);
+
+ isc_buffer_init(&buf, mem, sizeof(mem));
+ result = dns_rdataset_totext(&rds, name, false, false, &buf);
+
+ /*
+ * We could use xfrout_log(), but that would produce
+ * very long lines with a repetitive prefix.
+ */
+ if (result == ISC_R_SUCCESS) {
+ /*
+ * Get rid of final newline.
+ */
+ INSIST(buf.used >= 1 &&
+ ((char *)buf.base)[buf.used - 1] == '\n');
+ buf.used--;
+
+ isc_log_write(XFROUT_RR_LOGARGS, "%.*s",
+ (int)isc_buffer_usedlength(&buf),
+ (char *)isc_buffer_base(&buf));
+ } else {
+ isc_log_write(XFROUT_RR_LOGARGS, "<RR too large to print>");
+ }
+}
+
+/**************************************************************************/
+/*
+ * An 'rrstream_t' is a polymorphic iterator that returns
+ * a stream of resource records. There are multiple implementations,
+ * e.g. for generating AXFR and IXFR records streams.
+ */
+
+typedef struct rrstream_methods rrstream_methods_t;
+
+typedef struct rrstream {
+ isc_mem_t *mctx;
+ rrstream_methods_t *methods;
+} rrstream_t;
+
+struct rrstream_methods {
+ isc_result_t (*first)(rrstream_t *);
+ isc_result_t (*next)(rrstream_t *);
+ void (*current)(rrstream_t *, dns_name_t **, uint32_t *,
+ dns_rdata_t **);
+ void (*pause)(rrstream_t *);
+ void (*destroy)(rrstream_t **);
+};
+
+static void
+rrstream_noop_pause(rrstream_t *rs) {
+ UNUSED(rs);
+}
+
+/**************************************************************************/
+/*
+ * An 'ixfr_rrstream_t' is an 'rrstream_t' that returns
+ * an IXFR-like RR stream from a journal file.
+ *
+ * The SOA at the beginning of each sequence of additions
+ * or deletions are included in the stream, but the extra
+ * SOAs at the beginning and end of the entire transfer are
+ * not included.
+ */
+
+typedef struct ixfr_rrstream {
+ rrstream_t common;
+ dns_journal_t *journal;
+} ixfr_rrstream_t;
+
+/* Forward declarations. */
+static void
+ixfr_rrstream_destroy(rrstream_t **sp);
+
+static rrstream_methods_t ixfr_rrstream_methods;
+
+/*
+ * Returns: anything dns_journal_open() or dns_journal_iter_init()
+ * may return.
+ */
+
+static isc_result_t
+ixfr_rrstream_create(isc_mem_t *mctx, const char *journal_filename,
+ uint32_t begin_serial, uint32_t end_serial, size_t *sizep,
+ rrstream_t **sp) {
+ isc_result_t result;
+ ixfr_rrstream_t *s = NULL;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &ixfr_rrstream_methods;
+ s->journal = NULL;
+
+ CHECK(dns_journal_open(mctx, journal_filename, DNS_JOURNAL_READ,
+ &s->journal));
+ CHECK(dns_journal_iter_init(s->journal, begin_serial, end_serial,
+ sizep));
+
+ *sp = (rrstream_t *)s;
+ return (ISC_R_SUCCESS);
+
+failure:
+ ixfr_rrstream_destroy((rrstream_t **)(void *)&s);
+ return (result);
+}
+
+static isc_result_t
+ixfr_rrstream_first(rrstream_t *rs) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
+ return (dns_journal_first_rr(s->journal));
+}
+
+static isc_result_t
+ixfr_rrstream_next(rrstream_t *rs) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
+ return (dns_journal_next_rr(s->journal));
+}
+
+static void
+ixfr_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
+ dns_journal_current_rr(s->journal, name, ttl, rdata);
+}
+
+static void
+ixfr_rrstream_destroy(rrstream_t **rsp) {
+ ixfr_rrstream_t *s = (ixfr_rrstream_t *)*rsp;
+ if (s->journal != NULL) {
+ dns_journal_destroy(&s->journal);
+ }
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t ixfr_rrstream_methods = {
+ ixfr_rrstream_first, ixfr_rrstream_next, ixfr_rrstream_current,
+ rrstream_noop_pause, ixfr_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * An 'axfr_rrstream_t' is an 'rrstream_t' that returns
+ * an AXFR-like RR stream from a database.
+ *
+ * The SOAs at the beginning and end of the transfer are
+ * not included in the stream.
+ */
+
+typedef struct axfr_rrstream {
+ rrstream_t common;
+ dns_rriterator_t it;
+ bool it_valid;
+} axfr_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+axfr_rrstream_destroy(rrstream_t **rsp);
+
+static rrstream_methods_t axfr_rrstream_methods;
+
+static isc_result_t
+axfr_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
+ rrstream_t **sp) {
+ axfr_rrstream_t *s;
+ isc_result_t result;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &axfr_rrstream_methods;
+ s->it_valid = false;
+
+ CHECK(dns_rriterator_init(&s->it, db, ver, 0));
+ s->it_valid = true;
+
+ *sp = (rrstream_t *)s;
+ return (ISC_R_SUCCESS);
+
+failure:
+ axfr_rrstream_destroy((rrstream_t **)(void *)&s);
+ return (result);
+}
+
+static isc_result_t
+axfr_rrstream_first(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
+ isc_result_t result;
+ result = dns_rriterator_first(&s->it);
+ if (result != ISC_R_SUCCESS) {
+ return (result);
+ }
+ /* Skip SOA records. */
+ for (;;) {
+ dns_name_t *name_dummy = NULL;
+ uint32_t ttl_dummy;
+ dns_rdata_t *rdata = NULL;
+ dns_rriterator_current(&s->it, &name_dummy, &ttl_dummy, NULL,
+ &rdata);
+ if (rdata->type != dns_rdatatype_soa) {
+ break;
+ }
+ result = dns_rriterator_next(&s->it);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ return (result);
+}
+
+static isc_result_t
+axfr_rrstream_next(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
+ isc_result_t result;
+
+ /* Skip SOA records. */
+ for (;;) {
+ dns_name_t *name_dummy = NULL;
+ uint32_t ttl_dummy;
+ dns_rdata_t *rdata = NULL;
+ result = dns_rriterator_next(&s->it);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ dns_rriterator_current(&s->it, &name_dummy, &ttl_dummy, NULL,
+ &rdata);
+ if (rdata->type != dns_rdatatype_soa) {
+ break;
+ }
+ }
+ return (result);
+}
+
+static void
+axfr_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
+ dns_rriterator_current(&s->it, name, ttl, NULL, rdata);
+}
+
+static void
+axfr_rrstream_pause(rrstream_t *rs) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
+ dns_rriterator_pause(&s->it);
+}
+
+static void
+axfr_rrstream_destroy(rrstream_t **rsp) {
+ axfr_rrstream_t *s = (axfr_rrstream_t *)*rsp;
+ if (s->it_valid) {
+ dns_rriterator_destroy(&s->it);
+ }
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t axfr_rrstream_methods = {
+ axfr_rrstream_first, axfr_rrstream_next, axfr_rrstream_current,
+ axfr_rrstream_pause, axfr_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * An 'soa_rrstream_t' is a degenerate 'rrstream_t' that returns
+ * a single SOA record.
+ */
+
+typedef struct soa_rrstream {
+ rrstream_t common;
+ dns_difftuple_t *soa_tuple;
+} soa_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+soa_rrstream_destroy(rrstream_t **rsp);
+
+static rrstream_methods_t soa_rrstream_methods;
+
+static isc_result_t
+soa_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
+ rrstream_t **sp) {
+ soa_rrstream_t *s;
+ isc_result_t result;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &soa_rrstream_methods;
+ s->soa_tuple = NULL;
+
+ CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
+ &s->soa_tuple));
+
+ *sp = (rrstream_t *)s;
+ return (ISC_R_SUCCESS);
+
+failure:
+ soa_rrstream_destroy((rrstream_t **)(void *)&s);
+ return (result);
+}
+
+static isc_result_t
+soa_rrstream_first(rrstream_t *rs) {
+ UNUSED(rs);
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+soa_rrstream_next(rrstream_t *rs) {
+ UNUSED(rs);
+ return (ISC_R_NOMORE);
+}
+
+static void
+soa_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata) {
+ soa_rrstream_t *s = (soa_rrstream_t *)rs;
+ *name = &s->soa_tuple->name;
+ *ttl = s->soa_tuple->ttl;
+ *rdata = &s->soa_tuple->rdata;
+}
+
+static void
+soa_rrstream_destroy(rrstream_t **rsp) {
+ soa_rrstream_t *s = (soa_rrstream_t *)*rsp;
+ if (s->soa_tuple != NULL) {
+ dns_difftuple_free(&s->soa_tuple);
+ }
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t soa_rrstream_methods = {
+ soa_rrstream_first, soa_rrstream_next, soa_rrstream_current,
+ rrstream_noop_pause, soa_rrstream_destroy
+};
+
+/**************************************************************************/
+/*
+ * A 'compound_rrstream_t' objects owns a soa_rrstream
+ * and another rrstream, the "data stream". It returns
+ * a concatenated stream consisting of the soa_rrstream, then
+ * the data stream, then the soa_rrstream again.
+ *
+ * The component streams are owned by the compound_rrstream_t
+ * and are destroyed with it.
+ */
+
+typedef struct compound_rrstream {
+ rrstream_t common;
+ rrstream_t *components[3];
+ int state;
+ isc_result_t result;
+} compound_rrstream_t;
+
+/*
+ * Forward declarations.
+ */
+static void
+compound_rrstream_destroy(rrstream_t **rsp);
+
+static isc_result_t
+compound_rrstream_next(rrstream_t *rs);
+
+static rrstream_methods_t compound_rrstream_methods;
+
+/*
+ * Requires:
+ * soa_stream != NULL && *soa_stream != NULL
+ * data_stream != NULL && *data_stream != NULL
+ * sp != NULL && *sp == NULL
+ *
+ * Ensures:
+ * *soa_stream == NULL
+ * *data_stream == NULL
+ * *sp points to a valid compound_rrstream_t
+ * The soa and data streams will be destroyed
+ * when the compound_rrstream_t is destroyed.
+ */
+static isc_result_t
+compound_rrstream_create(isc_mem_t *mctx, rrstream_t **soa_stream,
+ rrstream_t **data_stream, rrstream_t **sp) {
+ compound_rrstream_t *s;
+
+ INSIST(sp != NULL && *sp == NULL);
+
+ s = isc_mem_get(mctx, sizeof(*s));
+ s->common.mctx = NULL;
+ isc_mem_attach(mctx, &s->common.mctx);
+ s->common.methods = &compound_rrstream_methods;
+ s->components[0] = *soa_stream;
+ s->components[1] = *data_stream;
+ s->components[2] = *soa_stream;
+ s->state = -1;
+ s->result = ISC_R_FAILURE;
+
+ *data_stream = NULL;
+ *soa_stream = NULL;
+ *sp = (rrstream_t *)s;
+ return (ISC_R_SUCCESS);
+}
+
+static isc_result_t
+compound_rrstream_first(rrstream_t *rs) {
+ compound_rrstream_t *s = (compound_rrstream_t *)rs;
+ s->state = 0;
+ do {
+ rrstream_t *curstream = s->components[s->state];
+ s->result = curstream->methods->first(curstream);
+ } while (s->result == ISC_R_NOMORE && s->state < 2);
+ return (s->result);
+}
+
+static isc_result_t
+compound_rrstream_next(rrstream_t *rs) {
+ compound_rrstream_t *s = (compound_rrstream_t *)rs;
+ rrstream_t *curstream = s->components[s->state];
+ s->result = curstream->methods->next(curstream);
+ while (s->result == ISC_R_NOMORE) {
+ /*
+ * Make sure locks held by the current stream
+ * are released before we switch streams.
+ */
+ curstream->methods->pause(curstream);
+ if (s->state == 2) {
+ return (ISC_R_NOMORE);
+ }
+ s->state++;
+ curstream = s->components[s->state];
+ s->result = curstream->methods->first(curstream);
+ }
+ return (s->result);
+}
+
+static void
+compound_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
+ dns_rdata_t **rdata) {
+ compound_rrstream_t *s = (compound_rrstream_t *)rs;
+ rrstream_t *curstream;
+ INSIST(0 <= s->state && s->state < 3);
+ INSIST(s->result == ISC_R_SUCCESS);
+ curstream = s->components[s->state];
+ curstream->methods->current(curstream, name, ttl, rdata);
+}
+
+static void
+compound_rrstream_pause(rrstream_t *rs) {
+ compound_rrstream_t *s = (compound_rrstream_t *)rs;
+ rrstream_t *curstream;
+ INSIST(0 <= s->state && s->state < 3);
+ curstream = s->components[s->state];
+ curstream->methods->pause(curstream);
+}
+
+static void
+compound_rrstream_destroy(rrstream_t **rsp) {
+ compound_rrstream_t *s = (compound_rrstream_t *)*rsp;
+ s->components[0]->methods->destroy(&s->components[0]);
+ s->components[1]->methods->destroy(&s->components[1]);
+ s->components[2] = NULL; /* Copy of components[0]. */
+ isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
+}
+
+static rrstream_methods_t compound_rrstream_methods = {
+ compound_rrstream_first, compound_rrstream_next,
+ compound_rrstream_current, compound_rrstream_pause,
+ compound_rrstream_destroy
+};
+
+/**************************************************************************/
+
+/*%
+ * Structure holding outgoing transfer statistics
+ */
+struct xfr_stats {
+ uint64_t nmsg; /*%< Number of messages sent */
+ uint64_t nrecs; /*%< Number of records sent */
+ uint64_t nbytes; /*%< Number of bytes sent */
+ isc_time_t start; /*%< Start time of the transfer */
+ isc_time_t end; /*%< End time of the transfer */
+};
+
+/*%
+ * An 'xfrout_ctx_t' contains the state of an outgoing AXFR or IXFR
+ * in progress.
+ */
+typedef struct {
+ isc_mem_t *mctx;
+ ns_client_t *client;
+ unsigned int id; /* ID of request */
+ dns_name_t *qname; /* Question name of request */
+ dns_rdatatype_t qtype; /* dns_rdatatype_{a,i}xfr */
+ dns_rdataclass_t qclass;
+ dns_zone_t *zone; /* (necessary for stats) */
+ dns_db_t *db;
+ dns_dbversion_t *ver;
+ isc_quota_t *quota;
+ rrstream_t *stream; /* The XFR RR stream */
+ bool question_added; /* QUESTION section sent? */
+ bool end_of_stream; /* EOS has been reached */
+ isc_buffer_t buf; /* Buffer for message owner
+ * names and rdatas */
+ isc_buffer_t txbuf; /* Transmit message buffer */
+ size_t cbytes; /* Length of current message */
+ void *txmem;
+ unsigned int txmemlen;
+ dns_tsigkey_t *tsigkey; /* Key used to create TSIG */
+ isc_buffer_t *lasttsig; /* the last TSIG */
+ bool verified_tsig; /* verified request MAC */
+ bool many_answers;
+ int sends; /* Send in progress */
+ bool shuttingdown;
+ bool poll;
+ const char *mnemonic; /* Style of transfer */
+ uint32_t end_serial; /* Serial number after XFR is done */
+ struct xfr_stats stats; /*%< Transfer statistics */
+
+ /* Timeouts */
+ uint64_t maxtime; /*%< Maximum XFR timeout (in ms) */
+ isc_nm_timer_t *maxtime_timer;
+
+ uint64_t idletime; /*%< XFR idle timeout (in ms) */
+} xfrout_ctx_t;
+
+static void
+xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client, unsigned int id,
+ dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_rdataclass_t qclass, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, isc_quota_t *quota, rrstream_t *stream,
+ dns_tsigkey_t *tsigkey, isc_buffer_t *lasttsig,
+ bool verified_tsig, unsigned int maxtime,
+ unsigned int idletime, bool many_answers,
+ xfrout_ctx_t **xfrp);
+
+static void
+sendstream(xfrout_ctx_t *xfr);
+
+static void
+xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg);
+
+static void
+xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg);
+
+static void
+xfrout_maybe_destroy(xfrout_ctx_t *xfr);
+
+static void
+xfrout_ctx_destroy(xfrout_ctx_t **xfrp);
+
+static void
+xfrout_client_timeout(void *arg, isc_result_t result);
+
+static void
+xfrout_log1(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
+ int level, const char *fmt, ...) ISC_FORMAT_PRINTF(5, 6);
+
+static void
+xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...)
+ ISC_FORMAT_PRINTF(3, 4);
+
+/**************************************************************************/
+
+void
+ns_xfr_start(ns_client_t *client, dns_rdatatype_t reqtype) {
+ isc_result_t result;
+ dns_name_t *question_name;
+ dns_rdataset_t *question_rdataset;
+ dns_zone_t *zone = NULL, *raw = NULL, *mayberaw;
+ dns_db_t *db = NULL;
+ dns_dbversion_t *ver = NULL;
+ dns_rdataclass_t question_class;
+ rrstream_t *soa_stream = NULL;
+ rrstream_t *data_stream = NULL;
+ rrstream_t *stream = NULL;
+ dns_difftuple_t *current_soa_tuple = NULL;
+ dns_name_t *soa_name;
+ dns_rdataset_t *soa_rdataset;
+ dns_rdata_t soa_rdata = DNS_RDATA_INIT;
+ bool have_soa = false;
+ const char *mnemonic = NULL;
+ isc_mem_t *mctx = client->mctx;
+ dns_message_t *request = client->message;
+ xfrout_ctx_t *xfr = NULL;
+ isc_quota_t *quota = NULL;
+ dns_transfer_format_t format = client->view->transfer_format;
+ isc_netaddr_t na;
+ dns_peer_t *peer = NULL;
+ isc_buffer_t *tsigbuf = NULL;
+ char *journalfile;
+ char msg[NS_CLIENT_ACLMSGSIZE("zone transfer")];
+ char keyname[DNS_NAME_FORMATSIZE];
+ bool is_poll = false;
+ bool is_dlz = false;
+ bool is_ixfr = false;
+ bool useviewacl = false;
+ uint32_t begin_serial = 0, current_serial;
+
+ switch (reqtype) {
+ case dns_rdatatype_axfr:
+ mnemonic = "AXFR";
+ break;
+ case dns_rdatatype_ixfr:
+ mnemonic = "IXFR";
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
+ ISC_LOG_DEBUG(6), "%s request", mnemonic);
+ /*
+ * Apply quota.
+ */
+ result = isc_quota_attach(&client->sctx->xfroutquota, &quota);
+ if (result != ISC_R_SUCCESS) {
+ isc_log_write(XFROUT_COMMON_LOGARGS, ISC_LOG_WARNING,
+ "%s request denied: %s", mnemonic,
+ isc_result_totext(result));
+ goto failure;
+ }
+
+ /*
+ * Interpret the question section.
+ */
+ result = dns_message_firstname(request, DNS_SECTION_QUESTION);
+ INSIST(result == ISC_R_SUCCESS);
+
+ /*
+ * The question section must contain exactly one question, and
+ * it must be for AXFR/IXFR as appropriate.
+ */
+ question_name = NULL;
+ dns_message_currentname(request, DNS_SECTION_QUESTION, &question_name);
+ question_rdataset = ISC_LIST_HEAD(question_name->list);
+ question_class = question_rdataset->rdclass;
+ INSIST(question_rdataset->type == reqtype);
+ if (ISC_LIST_NEXT(question_rdataset, link) != NULL) {
+ FAILC(DNS_R_FORMERR, "multiple questions");
+ }
+ result = dns_message_nextname(request, DNS_SECTION_QUESTION);
+ if (result != ISC_R_NOMORE) {
+ FAILC(DNS_R_FORMERR, "multiple questions");
+ }
+
+ result = dns_zt_find(client->view->zonetable, question_name, 0, NULL,
+ &zone);
+
+ if (result != ISC_R_SUCCESS || dns_zone_gettype(zone) == dns_zone_dlz) {
+ /*
+ * The normal zone table does not have a match, or this is
+ * marked in the zone table as a DLZ zone. Check the DLZ
+ * databases for a match.
+ */
+ if (!ISC_LIST_EMPTY(client->view->dlz_searched)) {
+ result = dns_dlzallowzonexfr(client->view,
+ question_name,
+ &client->peeraddr, &db);
+ if (result == ISC_R_DEFAULT) {
+ useviewacl = true;
+ result = ISC_R_SUCCESS;
+ }
+ if (result == ISC_R_NOPERM) {
+ char _buf1[DNS_NAME_FORMATSIZE];
+ char _buf2[DNS_RDATACLASS_FORMATSIZE];
+
+ result = DNS_R_REFUSED;
+ dns_name_format(question_name, _buf1,
+ sizeof(_buf1));
+ dns_rdataclass_format(question_class, _buf2,
+ sizeof(_buf2));
+ ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
+ NS_LOGMODULE_XFER_OUT,
+ ISC_LOG_ERROR,
+ "zone transfer '%s/%s' denied",
+ _buf1, _buf2);
+ goto failure;
+ }
+ if (result != ISC_R_SUCCESS) {
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ }
+ is_dlz = true;
+ } else {
+ /*
+ * not DLZ and not in normal zone table, we are
+ * not authoritative
+ */
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ }
+ } else {
+ /* zone table has a match */
+ switch (dns_zone_gettype(zone)) {
+ /*
+ * Primary, secondary, and mirror zones are OK for transfer.
+ */
+ case dns_zone_primary:
+ case dns_zone_secondary:
+ case dns_zone_mirror:
+ case dns_zone_dlz:
+ break;
+ default:
+ FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
+ question_name, question_class);
+ }
+ CHECK(dns_zone_getdb(zone, &db));
+ dns_db_currentversion(db, &ver);
+ }
+
+ xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
+ "%s question section OK", mnemonic);
+
+ /*
+ * Check the authority section. Look for a SOA record with
+ * the same name and class as the question.
+ */
+ for (result = dns_message_firstname(request, DNS_SECTION_AUTHORITY);
+ result == ISC_R_SUCCESS;
+ result = dns_message_nextname(request, DNS_SECTION_AUTHORITY))
+ {
+ soa_name = NULL;
+ dns_message_currentname(request, DNS_SECTION_AUTHORITY,
+ &soa_name);
+
+ /*
+ * Ignore data whose owner name is not the zone apex.
+ */
+ if (!dns_name_equal(soa_name, question_name)) {
+ continue;
+ }
+
+ for (soa_rdataset = ISC_LIST_HEAD(soa_name->list);
+ soa_rdataset != NULL;
+ soa_rdataset = ISC_LIST_NEXT(soa_rdataset, link))
+ {
+ /*
+ * Ignore non-SOA data.
+ */
+ if (soa_rdataset->type != dns_rdatatype_soa) {
+ continue;
+ }
+ if (soa_rdataset->rdclass != question_class) {
+ continue;
+ }
+
+ CHECK(dns_rdataset_first(soa_rdataset));
+ dns_rdataset_current(soa_rdataset, &soa_rdata);
+ result = dns_rdataset_next(soa_rdataset);
+ if (result == ISC_R_SUCCESS) {
+ FAILC(DNS_R_FORMERR, "IXFR authority section "
+ "has multiple SOAs");
+ }
+ have_soa = true;
+ goto got_soa;
+ }
+ }
+got_soa:
+ if (result != ISC_R_NOMORE) {
+ CHECK(result);
+ }
+
+ xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
+ "%s authority section OK", mnemonic);
+
+ /*
+ * If not a DLZ zone or we are falling back to the view's transfer
+ * ACL, decide whether to allow this transfer.
+ */
+ if (!is_dlz || useviewacl) {
+ dns_acl_t *acl;
+
+ ns_client_aclmsg("zone transfer", question_name, reqtype,
+ client->view->rdclass, msg, sizeof(msg));
+ if (useviewacl) {
+ acl = client->view->transferacl;
+ } else {
+ acl = dns_zone_getxfracl(zone);
+ }
+ CHECK(ns_client_checkacl(client, NULL, msg, acl, true,
+ ISC_LOG_ERROR));
+ }
+
+ /*
+ * AXFR over UDP is not possible.
+ */
+ if (reqtype == dns_rdatatype_axfr &&
+ (client->attributes & NS_CLIENTATTR_TCP) == 0)
+ {
+ FAILC(DNS_R_FORMERR, "attempted AXFR over UDP");
+ }
+
+ /*
+ * Look up the requesting server in the peer table.
+ */
+ isc_netaddr_fromsockaddr(&na, &client->peeraddr);
+ (void)dns_peerlist_peerbyaddr(client->view->peers, &na, &peer);
+
+ /*
+ * Decide on the transfer format (one-answer or many-answers).
+ */
+ if (peer != NULL) {
+ (void)dns_peer_gettransferformat(peer, &format);
+ }
+
+ /*
+ * Get a dynamically allocated copy of the current SOA.
+ */
+ if (is_dlz) {
+ dns_db_currentversion(db, &ver);
+ }
+
+ CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
+ &current_soa_tuple));
+
+ current_serial = dns_soa_getserial(&current_soa_tuple->rdata);
+ if (reqtype == dns_rdatatype_ixfr) {
+ size_t jsize;
+ uint64_t dbsize;
+
+ if (!have_soa) {
+ FAILC(DNS_R_FORMERR, "IXFR request missing SOA");
+ }
+
+ begin_serial = dns_soa_getserial(&soa_rdata);
+
+ /*
+ * RFC1995 says "If an IXFR query with the same or
+ * newer version number than that of the server
+ * is received, it is replied to with a single SOA
+ * record of the server's current version, just as
+ * in AXFR". The claim about AXFR is incorrect,
+ * but other than that, we do as the RFC says.
+ *
+ * Sending a single SOA record is also how we refuse
+ * IXFR over UDP (currently, we always do).
+ */
+ if (DNS_SERIAL_GE(begin_serial, current_serial) ||
+ (client->attributes & NS_CLIENTATTR_TCP) == 0)
+ {
+ CHECK(soa_rrstream_create(mctx, db, ver, &stream));
+ is_poll = true;
+ goto have_stream;
+ }
+
+ /*
+ * Outgoing IXFR may have been disabled for this peer
+ * or globally.
+ */
+ if ((client->attributes & NS_CLIENTATTR_TCP) != 0) {
+ bool provide_ixfr;
+
+ provide_ixfr = client->view->provideixfr;
+ if (peer != NULL) {
+ (void)dns_peer_getprovideixfr(peer,
+ &provide_ixfr);
+ }
+ if (!provide_ixfr) {
+ xfrout_log1(client, question_name,
+ question_class, ISC_LOG_DEBUG(4),
+ "IXFR delta response disabled due "
+ "to 'provide-ixfr no;' being set");
+ mnemonic = "AXFR-style IXFR";
+ goto axfr_fallback;
+ }
+ }
+
+ journalfile = is_dlz ? NULL : dns_zone_getjournal(zone);
+ if (journalfile != NULL) {
+ result = ixfr_rrstream_create(
+ mctx, journalfile, begin_serial, current_serial,
+ &jsize, &data_stream);
+ } else {
+ result = ISC_R_NOTFOUND;
+ }
+ if (result == ISC_R_NOTFOUND || result == ISC_R_RANGE) {
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_INFO,
+ "IXFR version not in journal, "
+ "falling back to AXFR");
+ mnemonic = "AXFR-style IXFR";
+ goto axfr_fallback;
+ }
+ CHECK(result);
+
+ result = dns_db_getsize(db, ver, NULL, &dbsize);
+ if (result == ISC_R_SUCCESS) {
+ uint32_t ratio = dns_zone_getixfrratio(zone);
+ if (ratio != 0 && ((100 * jsize) / dbsize) > ratio) {
+ data_stream->methods->destroy(&data_stream);
+ data_stream = NULL;
+ xfrout_log1(client, question_name,
+ question_class, ISC_LOG_INFO,
+ "IXFR delta size (%zu bytes) "
+ "exceeds the maximum ratio to "
+ "database size "
+ "(%" PRIu64 " bytes), "
+ "falling back to AXFR",
+ jsize, dbsize);
+ mnemonic = "AXFR-style IXFR";
+ goto axfr_fallback;
+ } else {
+ xfrout_log1(client, question_name,
+ question_class, ISC_LOG_DEBUG(4),
+ "IXFR delta size (%zu bytes); "
+ "database size "
+ "(%" PRIu64 " bytes)",
+ jsize, dbsize);
+ }
+ }
+ is_ixfr = true;
+ } else {
+ axfr_fallback:
+ CHECK(axfr_rrstream_create(mctx, db, ver, &data_stream));
+ }
+
+ /*
+ * Bracket the data stream with SOAs.
+ */
+ CHECK(soa_rrstream_create(mctx, db, ver, &soa_stream));
+ CHECK(compound_rrstream_create(mctx, &soa_stream, &data_stream,
+ &stream));
+ soa_stream = NULL;
+ data_stream = NULL;
+
+have_stream:
+ CHECK(dns_message_getquerytsig(request, mctx, &tsigbuf));
+ /*
+ * Create the xfrout context object. This transfers the ownership
+ * of "stream", "db", "ver", and "quota" to the xfrout context object.
+ */
+
+ if (is_dlz) {
+ xfrout_ctx_create(mctx, client, request->id, question_name,
+ reqtype, question_class, zone, db, ver, quota,
+ stream, dns_message_gettsigkey(request),
+ tsigbuf, request->verified_sig, 3600, 3600,
+ (format == dns_many_answers) ? true : false,
+ &xfr);
+ } else {
+ xfrout_ctx_create(
+ mctx, client, request->id, question_name, reqtype,
+ question_class, zone, db, ver, quota, stream,
+ dns_message_gettsigkey(request), tsigbuf,
+ request->verified_sig, dns_zone_getmaxxfrout(zone),
+ dns_zone_getidleout(zone),
+ (format == dns_many_answers) ? true : false, &xfr);
+ }
+
+ xfr->end_serial = current_serial;
+ xfr->mnemonic = mnemonic;
+ stream = NULL;
+ quota = NULL;
+
+ CHECK(xfr->stream->methods->first(xfr->stream));
+
+ if (xfr->tsigkey != NULL) {
+ dns_name_format(&xfr->tsigkey->name, keyname, sizeof(keyname));
+ } else {
+ keyname[0] = '\0';
+ }
+ xfr->poll = is_poll;
+ if (is_poll) {
+ xfr->mnemonic = "IXFR poll response";
+ xfrout_log1(client, question_name, question_class,
+ ISC_LOG_DEBUG(1), "IXFR poll up to date%s%s",
+ (xfr->tsigkey != NULL) ? ": TSIG " : "", keyname);
+ } else if (is_ixfr) {
+ xfrout_log1(client, question_name, question_class, ISC_LOG_INFO,
+ "%s started%s%s (serial %u -> %u)", mnemonic,
+ (xfr->tsigkey != NULL) ? ": TSIG " : "", keyname,
+ begin_serial, current_serial);
+ } else {
+ xfrout_log1(client, question_name, question_class, ISC_LOG_INFO,
+ "%s started%s%s (serial %u)", mnemonic,
+ (xfr->tsigkey != NULL) ? ": TSIG " : "", keyname,
+ current_serial);
+ }
+
+ if (zone != NULL) {
+ dns_zone_getraw(zone, &raw);
+ mayberaw = (raw != NULL) ? raw : zone;
+ if ((client->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0 &&
+ (dns_zone_gettype(mayberaw) == dns_zone_secondary ||
+ dns_zone_gettype(mayberaw) == dns_zone_mirror))
+ {
+ isc_time_t expiretime;
+ uint32_t secs;
+ dns_zone_getexpiretime(zone, &expiretime);
+ secs = isc_time_seconds(&expiretime);
+ if (secs >= client->now && result == ISC_R_SUCCESS) {
+ client->attributes |= NS_CLIENTATTR_HAVEEXPIRE;
+ client->expire = secs - client->now;
+ }
+ }
+ if (raw != NULL) {
+ dns_zone_detach(&raw);
+ }
+ }
+
+ /* Start the timers */
+ if (xfr->maxtime > 0) {
+ xfrout_log(xfr, ISC_LOG_DEBUG(1),
+ "starting maxtime timer %" PRIu64 " ms",
+ xfr->maxtime);
+ isc_nm_timer_start(xfr->maxtime_timer, xfr->maxtime);
+ }
+
+ /*
+ * Hand the context over to sendstream(). Set xfr to NULL;
+ * sendstream() is responsible for either passing the
+ * context on to a later event handler or destroying it.
+ */
+ sendstream(xfr);
+ xfr = NULL;
+
+ result = ISC_R_SUCCESS;
+
+failure:
+ if (result == DNS_R_REFUSED) {
+ inc_stats(client, zone, ns_statscounter_xfrrej);
+ }
+ if (quota != NULL) {
+ isc_quota_detach(&quota);
+ }
+ if (current_soa_tuple != NULL) {
+ dns_difftuple_free(&current_soa_tuple);
+ }
+ if (stream != NULL) {
+ stream->methods->destroy(&stream);
+ }
+ if (soa_stream != NULL) {
+ soa_stream->methods->destroy(&soa_stream);
+ }
+ if (data_stream != NULL) {
+ data_stream->methods->destroy(&data_stream);
+ }
+ if (ver != NULL) {
+ dns_db_closeversion(db, &ver, false);
+ }
+ if (db != NULL) {
+ dns_db_detach(&db);
+ }
+ if (zone != NULL) {
+ dns_zone_detach(&zone);
+ }
+ /* XXX kludge */
+ if (xfr != NULL) {
+ xfrout_fail(xfr, result, "setting up zone transfer");
+ } else if (result != ISC_R_SUCCESS) {
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
+ NS_LOGMODULE_XFER_OUT, ISC_LOG_DEBUG(3),
+ "zone transfer setup failed");
+ ns_client_error(client, result);
+ isc_nmhandle_detach(&client->reqhandle);
+ }
+}
+
+static void
+xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client, unsigned int id,
+ dns_name_t *qname, dns_rdatatype_t qtype,
+ dns_rdataclass_t qclass, dns_zone_t *zone, dns_db_t *db,
+ dns_dbversion_t *ver, isc_quota_t *quota, rrstream_t *stream,
+ dns_tsigkey_t *tsigkey, isc_buffer_t *lasttsig,
+ bool verified_tsig, unsigned int maxtime,
+ unsigned int idletime, bool many_answers,
+ xfrout_ctx_t **xfrp) {
+ xfrout_ctx_t *xfr = NULL;
+ unsigned int len = NS_CLIENT_TCP_BUFFER_SIZE;
+ void *mem = NULL;
+
+ REQUIRE(xfrp != NULL && *xfrp == NULL);
+
+ xfr = isc_mem_get(mctx, sizeof(*xfr));
+ *xfr = (xfrout_ctx_t){
+ .client = client,
+ .id = id,
+ .qname = qname,
+ .qtype = qtype,
+ .qclass = qclass,
+ .maxtime = maxtime * 1000, /* in milliseconds */
+ .idletime = idletime * 1000, /* In milliseconds */
+ .tsigkey = tsigkey,
+ .lasttsig = lasttsig,
+ .verified_tsig = verified_tsig,
+ .many_answers = many_answers,
+ };
+
+ isc_mem_attach(mctx, &xfr->mctx);
+
+ if (zone != NULL) { /* zone will be NULL if it's DLZ */
+ dns_zone_attach(zone, &xfr->zone);
+ }
+ dns_db_attach(db, &xfr->db);
+ dns_db_attachversion(db, ver, &xfr->ver);
+
+ isc_time_now(&xfr->stats.start);
+
+ isc_nm_timer_create(xfr->client->handle, xfrout_client_timeout, xfr,
+ &xfr->maxtime_timer);
+
+ /*
+ * Allocate a temporary buffer for the uncompressed response
+ * message data. The buffer size must be 65535 bytes
+ * (NS_CLIENT_TCP_BUFFER_SIZE): small enough that compressed
+ * data will fit in a single TCP message, and big enough to
+ * hold a maximum-sized RR.
+ *
+ * Note that although 65535-byte RRs are allowed in principle, they
+ * cannot be zone-transferred (at least not if uncompressible),
+ * because the message and RR headers would push the size of the
+ * TCP message over the 65536 byte limit.
+ */
+ mem = isc_mem_get(mctx, len);
+ isc_buffer_init(&xfr->buf, mem, len);
+
+ /*
+ * Allocate another temporary buffer for the compressed
+ * response message.
+ */
+ mem = isc_mem_get(mctx, len);
+ isc_buffer_init(&xfr->txbuf, (char *)mem, len);
+ xfr->txmem = mem;
+ xfr->txmemlen = len;
+
+ /*
+ * These MUST be after the last "goto failure;" / CHECK to
+ * prevent a double free by the caller.
+ */
+ xfr->quota = quota;
+ xfr->stream = stream;
+
+ *xfrp = xfr;
+}
+
+/*
+ * Arrange to send as much as we can of "stream" without blocking.
+ *
+ * Requires:
+ * The stream iterator is initialized and points at an RR,
+ * or possibly at the end of the stream (that is, the
+ * _first method of the iterator has been called).
+ */
+static void
+sendstream(xfrout_ctx_t *xfr) {
+ dns_message_t *tcpmsg = NULL;
+ dns_message_t *msg = NULL; /* Client message if UDP, tcpmsg if TCP */
+ isc_result_t result;
+ dns_rdataset_t *qrdataset;
+ dns_name_t *msgname = NULL;
+ dns_rdata_t *msgrdata = NULL;
+ dns_rdatalist_t *msgrdl = NULL;
+ dns_rdataset_t *msgrds = NULL;
+ dns_compress_t cctx;
+ bool cleanup_cctx = false;
+ bool is_tcp;
+ int n_rrs;
+
+ isc_buffer_clear(&xfr->buf);
+ isc_buffer_clear(&xfr->txbuf);
+
+ is_tcp = ((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0);
+ if (!is_tcp) {
+ /*
+ * In the UDP case, we put the response data directly into
+ * the client message.
+ */
+ msg = xfr->client->message;
+ CHECK(dns_message_reply(msg, true));
+ } else {
+ /*
+ * TCP. Build a response dns_message_t, temporarily storing
+ * the raw, uncompressed owner names and RR data contiguously
+ * in xfr->buf. We know that if the uncompressed data fits
+ * in xfr->buf, the compressed data will surely fit in a TCP
+ * message.
+ */
+
+ dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER,
+ &tcpmsg);
+ msg = tcpmsg;
+
+ msg->id = xfr->id;
+ msg->rcode = dns_rcode_noerror;
+ msg->flags = DNS_MESSAGEFLAG_QR | DNS_MESSAGEFLAG_AA;
+ if ((xfr->client->attributes & NS_CLIENTATTR_RA) != 0) {
+ msg->flags |= DNS_MESSAGEFLAG_RA;
+ }
+ CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
+ CHECK(dns_message_setquerytsig(msg, xfr->lasttsig));
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+ msg->verified_sig = xfr->verified_tsig;
+
+ /*
+ * Add a EDNS option to the message?
+ */
+ if ((xfr->client->attributes & NS_CLIENTATTR_WANTOPT) != 0) {
+ dns_rdataset_t *opt = NULL;
+
+ CHECK(ns_client_addopt(xfr->client, msg, &opt));
+ CHECK(dns_message_setopt(msg, opt));
+ /*
+ * Add to first message only.
+ */
+ xfr->client->attributes &= ~NS_CLIENTATTR_WANTNSID;
+ xfr->client->attributes &= ~NS_CLIENTATTR_HAVEEXPIRE;
+ }
+
+ /*
+ * Account for reserved space.
+ */
+ if (xfr->tsigkey != NULL) {
+ INSIST(msg->reserved != 0U);
+ }
+ isc_buffer_add(&xfr->buf, msg->reserved);
+
+ /*
+ * Include a question section in the first message only.
+ * BIND 8.2.1 will not recognize an IXFR if it does not
+ * have a question section.
+ */
+ if (!xfr->question_added) {
+ dns_name_t *qname = NULL;
+ isc_region_t r;
+
+ /*
+ * Reserve space for the 12-byte message header
+ * and 4 bytes of question.
+ */
+ isc_buffer_add(&xfr->buf, 12 + 4);
+
+ qrdataset = NULL;
+ result = dns_message_gettemprdataset(msg, &qrdataset);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ dns_rdataset_makequestion(qrdataset,
+ xfr->client->message->rdclass,
+ xfr->qtype);
+
+ result = dns_message_gettempname(msg, &qname);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ isc_buffer_availableregion(&xfr->buf, &r);
+ INSIST(r.length >= xfr->qname->length);
+ r.length = xfr->qname->length;
+ isc_buffer_putmem(&xfr->buf, xfr->qname->ndata,
+ xfr->qname->length);
+ dns_name_fromregion(qname, &r);
+ ISC_LIST_INIT(qname->list);
+ ISC_LIST_APPEND(qname->list, qrdataset, link);
+
+ dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
+ xfr->question_added = true;
+ } else {
+ /*
+ * Reserve space for the 12-byte message header
+ */
+ isc_buffer_add(&xfr->buf, 12);
+ msg->tcp_continuation = 1;
+ }
+ }
+
+ /*
+ * Try to fit in as many RRs as possible, unless "one-answer"
+ * format has been requested.
+ */
+ for (n_rrs = 0;; n_rrs++) {
+ dns_name_t *name = NULL;
+ uint32_t ttl;
+ dns_rdata_t *rdata = NULL;
+
+ unsigned int size;
+ isc_region_t r;
+
+ msgname = NULL;
+ msgrdata = NULL;
+ msgrdl = NULL;
+ msgrds = NULL;
+
+ xfr->stream->methods->current(xfr->stream, &name, &ttl, &rdata);
+ size = name->length + 10 + rdata->length;
+ isc_buffer_availableregion(&xfr->buf, &r);
+ if (size >= r.length) {
+ /*
+ * RR would not fit. If there are other RRs in the
+ * buffer, send them now and leave this RR to the
+ * next message. If this RR overflows the buffer
+ * all by itself, fail.
+ *
+ * In theory some RRs might fit in a TCP message
+ * when compressed even if they do not fit when
+ * uncompressed, but surely we don't want
+ * to send such monstrosities to an unsuspecting
+ * secondary.
+ */
+ if (n_rrs == 0) {
+ xfrout_log(xfr, ISC_LOG_WARNING,
+ "RR too large for zone transfer "
+ "(%d bytes)",
+ size);
+ /* XXX DNS_R_RRTOOLARGE? */
+ result = ISC_R_NOSPACE;
+ goto failure;
+ }
+ break;
+ }
+
+ if (isc_log_wouldlog(ns_lctx, XFROUT_RR_LOGLEVEL)) {
+ log_rr(name, rdata, ttl); /* XXX */
+ }
+
+ result = dns_message_gettempname(msg, &msgname);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ isc_buffer_availableregion(&xfr->buf, &r);
+ INSIST(r.length >= name->length);
+ r.length = name->length;
+ isc_buffer_putmem(&xfr->buf, name->ndata, name->length);
+ dns_name_fromregion(msgname, &r);
+
+ /* Reserve space for RR header. */
+ isc_buffer_add(&xfr->buf, 10);
+
+ result = dns_message_gettemprdata(msg, &msgrdata);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ isc_buffer_availableregion(&xfr->buf, &r);
+ r.length = rdata->length;
+ isc_buffer_putmem(&xfr->buf, rdata->data, rdata->length);
+ dns_rdata_init(msgrdata);
+ dns_rdata_fromregion(msgrdata, rdata->rdclass, rdata->type, &r);
+
+ result = dns_message_gettemprdatalist(msg, &msgrdl);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ msgrdl->type = rdata->type;
+ msgrdl->rdclass = rdata->rdclass;
+ msgrdl->ttl = ttl;
+ if (rdata->type == dns_rdatatype_sig ||
+ rdata->type == dns_rdatatype_rrsig)
+ {
+ msgrdl->covers = dns_rdata_covers(rdata);
+ } else {
+ msgrdl->covers = dns_rdatatype_none;
+ }
+ ISC_LIST_APPEND(msgrdl->rdata, msgrdata, link);
+
+ result = dns_message_gettemprdataset(msg, &msgrds);
+ if (result != ISC_R_SUCCESS) {
+ goto failure;
+ }
+ result = dns_rdatalist_tordataset(msgrdl, msgrds);
+ INSIST(result == ISC_R_SUCCESS);
+
+ ISC_LIST_APPEND(msgname->list, msgrds, link);
+
+ dns_message_addname(msg, msgname, DNS_SECTION_ANSWER);
+ msgname = NULL;
+
+ xfr->stats.nrecs++;
+
+ result = xfr->stream->methods->next(xfr->stream);
+ if (result == ISC_R_NOMORE) {
+ xfr->end_of_stream = true;
+ break;
+ }
+ CHECK(result);
+
+ if (!xfr->many_answers) {
+ break;
+ }
+ /*
+ * At this stage, at least 1 RR has been rendered into
+ * the message. Check if we want to clamp this message
+ * here (TCP only).
+ */
+ if ((isc_buffer_usedlength(&xfr->buf) >=
+ xfr->client->sctx->transfer_tcp_message_size) &&
+ is_tcp)
+ {
+ break;
+ }
+ }
+
+ if (is_tcp) {
+ isc_region_t used;
+ CHECK(dns_compress_init(&cctx, -1, xfr->mctx));
+ dns_compress_setsensitive(&cctx, true);
+ cleanup_cctx = true;
+ CHECK(dns_message_renderbegin(msg, &cctx, &xfr->txbuf));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
+ CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
+ CHECK(dns_message_renderend(msg));
+ dns_compress_invalidate(&cctx);
+ cleanup_cctx = false;
+
+ isc_buffer_usedregion(&xfr->txbuf, &used);
+
+ xfrout_log(xfr, ISC_LOG_DEBUG(8),
+ "sending TCP message of %d bytes", used.length);
+
+ /* System test helper options to simulate network issues. */
+ if (ns_server_getoption(xfr->client->manager->sctx,
+ NS_SERVER_TRANSFERSLOWLY))
+ {
+ /* Sleep for a bit over a second. */
+ select(0, NULL, NULL, NULL,
+ &(struct timeval){ 1, 1000 });
+ }
+ if (ns_server_getoption(xfr->client->manager->sctx,
+ NS_SERVER_TRANSFERSTUCK))
+ {
+ /* Sleep for a bit over a minute. */
+ select(0, NULL, NULL, NULL,
+ &(struct timeval){ 60, 1000 });
+ }
+
+ isc_nmhandle_attach(xfr->client->handle,
+ &xfr->client->sendhandle);
+ if (xfr->idletime > 0) {
+ isc_nmhandle_setwritetimeout(xfr->client->sendhandle,
+ xfr->idletime);
+ }
+ isc_nm_send(xfr->client->sendhandle, &used, xfrout_senddone,
+ xfr);
+ xfr->sends++;
+ xfr->cbytes = used.length;
+ } else {
+ xfrout_log(xfr, ISC_LOG_DEBUG(8), "sending IXFR UDP response");
+
+ /* System test helper options to simulate network issues. */
+ if (ns_server_getoption(xfr->client->manager->sctx,
+ NS_SERVER_TRANSFERSLOWLY))
+ {
+ /* Sleep for a bit over a second. */
+ select(0, NULL, NULL, NULL,
+ &(struct timeval){ 1, 1000 });
+ }
+ if (ns_server_getoption(xfr->client->manager->sctx,
+ NS_SERVER_TRANSFERSTUCK))
+ {
+ /* Sleep for a bit over a minute. */
+ select(0, NULL, NULL, NULL,
+ &(struct timeval){ 60, 1000 });
+ }
+
+ ns_client_send(xfr->client);
+ xfr->stream->methods->pause(xfr->stream);
+ isc_nmhandle_detach(&xfr->client->reqhandle);
+ xfrout_ctx_destroy(&xfr);
+ return;
+ }
+
+ /* Advance lasttsig to be the last TSIG generated */
+ CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
+
+failure:
+ if (msgname != NULL) {
+ if (msgrds != NULL) {
+ if (dns_rdataset_isassociated(msgrds)) {
+ dns_rdataset_disassociate(msgrds);
+ }
+ dns_message_puttemprdataset(msg, &msgrds);
+ }
+ if (msgrdl != NULL) {
+ ISC_LIST_UNLINK(msgrdl->rdata, msgrdata, link);
+ dns_message_puttemprdatalist(msg, &msgrdl);
+ }
+ if (msgrdata != NULL) {
+ dns_message_puttemprdata(msg, &msgrdata);
+ }
+ dns_message_puttempname(msg, &msgname);
+ }
+
+ if (tcpmsg != NULL) {
+ dns_message_detach(&tcpmsg);
+ }
+
+ if (cleanup_cctx) {
+ dns_compress_invalidate(&cctx);
+ }
+ /*
+ * Make sure to release any locks held by database
+ * iterators before returning from the event handler.
+ */
+ xfr->stream->methods->pause(xfr->stream);
+
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+
+ if (xfr->client->sendhandle != NULL) {
+ isc_nmhandle_detach(&xfr->client->sendhandle);
+ }
+
+ xfrout_fail(xfr, result, "sending zone data");
+}
+
+static void
+xfrout_ctx_destroy(xfrout_ctx_t **xfrp) {
+ xfrout_ctx_t *xfr = *xfrp;
+ *xfrp = NULL;
+
+ INSIST(xfr->sends == 0);
+
+ isc_nm_timer_stop(xfr->maxtime_timer);
+ isc_nm_timer_detach(&xfr->maxtime_timer);
+
+ if (xfr->stream != NULL) {
+ xfr->stream->methods->destroy(&xfr->stream);
+ }
+ if (xfr->buf.base != NULL) {
+ isc_mem_put(xfr->mctx, xfr->buf.base, xfr->buf.length);
+ }
+ if (xfr->txmem != NULL) {
+ isc_mem_put(xfr->mctx, xfr->txmem, xfr->txmemlen);
+ }
+ if (xfr->lasttsig != NULL) {
+ isc_buffer_free(&xfr->lasttsig);
+ }
+ if (xfr->quota != NULL) {
+ isc_quota_detach(&xfr->quota);
+ }
+ if (xfr->ver != NULL) {
+ dns_db_closeversion(xfr->db, &xfr->ver, false);
+ }
+ if (xfr->zone != NULL) {
+ dns_zone_detach(&xfr->zone);
+ }
+ if (xfr->db != NULL) {
+ dns_db_detach(&xfr->db);
+ }
+
+ isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
+}
+
+static void
+xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
+ xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg;
+
+ REQUIRE((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0);
+
+ INSIST(handle == xfr->client->handle);
+
+ xfr->sends--;
+ INSIST(xfr->sends == 0);
+
+ isc_nmhandle_detach(&xfr->client->sendhandle);
+
+ /*
+ * Update transfer statistics if sending succeeded, accounting for the
+ * two-byte TCP length prefix included in the number of bytes sent.
+ */
+ if (result == ISC_R_SUCCESS) {
+ xfr->stats.nmsg++;
+ xfr->stats.nbytes += xfr->cbytes;
+ }
+
+ if (xfr->shuttingdown) {
+ xfrout_maybe_destroy(xfr);
+ } else if (result != ISC_R_SUCCESS) {
+ xfrout_fail(xfr, result, "send");
+ } else if (!xfr->end_of_stream) {
+ sendstream(xfr);
+ } else {
+ /* End of zone transfer stream. */
+ uint64_t msecs, persec;
+
+ inc_stats(xfr->client, xfr->zone, ns_statscounter_xfrdone);
+ isc_time_now(&xfr->stats.end);
+ msecs = isc_time_microdiff(&xfr->stats.end, &xfr->stats.start);
+ msecs /= 1000;
+ if (msecs == 0) {
+ msecs = 1;
+ }
+ persec = (xfr->stats.nbytes * 1000) / msecs;
+ xfrout_log(xfr, xfr->poll ? ISC_LOG_DEBUG(1) : ISC_LOG_INFO,
+ "%s ended: "
+ "%" PRIu64 " messages, %" PRIu64 " records, "
+ "%" PRIu64 " bytes, "
+ "%u.%03u secs (%u bytes/sec) (serial %u)",
+ xfr->mnemonic, xfr->stats.nmsg, xfr->stats.nrecs,
+ xfr->stats.nbytes, (unsigned int)(msecs / 1000),
+ (unsigned int)(msecs % 1000), (unsigned int)persec,
+ xfr->end_serial);
+
+ /*
+ * We're done, unreference the handle and destroy the xfr
+ * context.
+ */
+ isc_nmhandle_detach(&xfr->client->reqhandle);
+ xfrout_ctx_destroy(&xfr);
+ }
+}
+
+static void
+xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg) {
+ xfr->shuttingdown = true;
+ xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
+ isc_result_totext(result));
+ xfrout_maybe_destroy(xfr);
+}
+
+static void
+xfrout_maybe_destroy(xfrout_ctx_t *xfr) {
+ REQUIRE(xfr->shuttingdown);
+
+ ns_client_drop(xfr->client, ISC_R_CANCELED);
+ isc_nmhandle_detach(&xfr->client->reqhandle);
+ xfrout_ctx_destroy(&xfr);
+}
+
+static void
+xfrout_client_timeout(void *arg, isc_result_t result) {
+ xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg;
+
+ xfr->shuttingdown = true;
+ xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s", "aborted",
+ isc_result_totext(result));
+}
+
+/*
+ * Log outgoing zone transfer messages in a format like
+ * <client>: transfer of <zone>: <message>
+ */
+
+static void
+xfrout_logv(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
+ int level, const char *fmt, va_list ap) ISC_FORMAT_PRINTF(5, 0);
+
+static void
+xfrout_logv(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
+ int level, const char *fmt, va_list ap) {
+ char msgbuf[2048];
+ char namebuf[DNS_NAME_FORMATSIZE];
+ char classbuf[DNS_RDATACLASS_FORMATSIZE];
+
+ dns_name_format(zonename, namebuf, sizeof(namebuf));
+ dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf));
+ vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
+ ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
+ level, "transfer of '%s/%s': %s", namebuf, classbuf,
+ msgbuf);
+}
+
+/*
+ * Logging function for use when a xfrout_ctx_t has not yet been created.
+ */
+static void
+xfrout_log1(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
+ int level, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ xfrout_logv(client, zonename, rdclass, level, fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Logging function for use when there is a xfrout_ctx_t.
+ */
+static void
+xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ xfrout_logv(xfr->client, xfr->qname, xfr->qclass, level, fmt, ap);
+ va_end(ap);
+}